I finally figured it out. There were a number of main reasons that were further prevented by the added dose of cross-platform variability.
JFreeChart renders text in the wrong place because it uses a different font object
The layout problem arose because JFreeChart inadvertently calculated metrics for the layout using a different Font object than the one that AWT actually uses to render the font. (For reference, JFreeChart is calculated in org.jfree.text#getTextBounds .)
The reason for the other Font object is the result of the implicit “magic manipulation” mentioned in the question, which is executed inside java.awt.font.TextLayout#singleFont .
These three lines of magical manipulation can be summarized as follows:
font = Font.getFont(font.getAttributes())
In English, this asks the font manager to provide us with a new Font object based on the "attributes" (first name, last name, dot size, etc.) of the supplied font. Under certain circumstances, the Font that it returns to you will be different from the Font that you started from the beginning.
To fix the metrics (and thus fix the layout), to fix it, you need to run the one-line above on your own Font object before installing the font in the JFreeChart objects .
After that, the layout worked fine for me, as did the Japanese heroes. It should also fix the layout for you, although it may not show you Japanese characters. Read below about native fonts to see why.
Mac OS X Font Manager prefers to return the original fonts, even if you download a physical TTF file
The layout of the text has been fixed by the above change ... but why is this happening? Under what circumstances does FontManager actually return a different type of Font object to us than the one we provided?
There are many reasons, but at least on Mac OS X, the reason for the problem is that the font manager seems to prefer to return the native fonts whenever possible.
In other words, if you create a new font from a physical TTF font called "Foobar" using Font.createFont , and then call Font.getFont () with attributes derived from your physical Foobar font ... for so long since OS X already has Foobar font, the font manager will return the CFont object, not the TrueTypeFont object that you expected. This seems to persist even if you register the font through GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont RegisterFont.
In my case, it threw the red herring into the investigation: I already had the font "Source Sans" installed on my Mac, which meant that I got different results from people who did not.
Mac OS X Native Fonts Always Support Asian Characters
The bottom line is that Mac OS X CFont always support Asian character sets. I don’t understand what kind of mechanism allows this, but I suspect that this is some kind of font backup function of OS X itself, not Java. In any case, a CFont always pretends (and indeed can) display Asian characters with the correct glyphs.
This allows us to understand the mechanism that allowed us to create the original problem:
- we created a physical
Font from a physical TTF file, which in itself does not support Japanese. - the same physical font as above was also installed in my Mac OS X font book
- when calculating the chart diagram, JFreeChart specified the
Font physical object for the Japanese text metrics. Physical Font could not do this correctly because it does not support Asian character sets. - when drawing a chart, the magic manipulation in
TextLayout#singleFont forced him to get a CFont object and draw a glyph using a native font with the same name compared to the physical TrueTypeFont . Thus, the glyphs were correct, but they were incorrectly positioned.
You will get different results depending on whether you have a font registered and if you have a font installed on your operating system
If you call Font.getFont() with the attributes from the generated TTF font, you will get one of three different results depending on whether the font is registered and if you have the same font installed:
- If you have do font installed on your native platform with the same name as your TTF font (regardless of whether you registered this font or not), you will receive Asian
CFont support for the desired font. - If you registered a TTF
Font in a GraphicsEnvironment but don’t have a native font of the same name, calling Font.getFont () will return a TrueTypeFont physical object. This gives you the font you want, but you won’t get Asian characters. - If you have not registered TTF
Font , and you also have your own font with the same name, calling Font.getFont () will return Asian CFont, but it will not be the requested font.
Looking back, none of this is surprising. Leading:
I accidentally used the wrong font
In the production application, I created the font, but I forgot to register it first in the GraphicsEnvironment. If you have not registered the font while performing the magic manipulations above, Font.getFont() does not know how to get it, and instead you will get a backup font. Unfortunately.
On Windows, Mac, and Linux, this fallback font is typically Dialog, which is a logical (composite) font that supports Asian characters. At least in Java 7u72, the default Dialog font has the following fonts for Western alphabets:
- Mac: Lucida Grande
- Linux (CentOS): Lucida Sans
- Windows: Arial
This error was actually good for our Asian users because it meant that their character sets looked as expected with a logical font ... although western users did not get the character sets we wanted.
Since it was rendering in the wrong fonts, and we still needed to fix the Japanese layout, I decided that I better try to standardize one common font for future releases (and thus come closer to the trashgod suggestions).
In addition, the application has font quality rendering requirements that may not always allow the use of certain fonts, so a reasonable solution seemed to be trying to configure the application to use Lucida Sans, which is one physical font that is included by Oracle in all Java instances. But...
Lucida Sans doesn't play well with Asian characters on all platforms
The decision to try Lucida Sans seemed reasonable ... but I quickly found that there were differences in the platform how Lucida Sans is processed. On Linux and Windows, if you ask for a copy of the "Lucida Sans" font, you will get a TrueTypeFont physical object. But this font does not support Asian characters.
The same applies to Mac OS X if you request “Lucida Sans” ... but if you ask for a slightly different name “LucidaSans” (note the lack of space), you will get a CFont object that supports Lucida Sans, as well as Asian characters so you can get your cake and eat it too.
On other platforms, the query "LucidaSans" gives a copy of the standard Dialog font, because there is no such font, and Java returns by default. On Linux, you're in luck because Dialog actually uses Lucida Sans for western text by default (and also uses a decent replacement font for Asian characters).
This gives us a way to get (almost) the same physical font on all platforms and which also supports Asian characters by requesting fonts with these names:
- Mac OS X: "LucidaSans" (inferior to Lucida Sans + Asian fallback fonts)
- Linux: Dialog (Lucida Sans output + Asian fallback fonts)
- Windows: Dialog (Arial + Asian backup fonts are displayed)
I looked at the fonts.properties files on Windows, and I could not find the font sequence that was the default for Lucida Sans, so it looks like our Windows users will have to get stuck with Arial ... but at least it is not which is visually different from Lucida Sans, and the quality of rendering Windows fonts is reasonable.
Where did it all end?
In general, now we are almost just using platform fonts. (I'm sure @trashgod has a laugh now!) Both Mac and Linux servers get Lucida Sans, Windows gets Arial, the rendering quality is good, and everyone is happy!