使用 JFreeChart 渲染图表时,当图表的类别标签包含日文字符时,我注意到布局问题。尽管文本以正确的字形呈现,但文本被放置在错误的位置,可能是因为字体度量错误。
该图表最初配置为对该文本使用Source Sans Pro Regular字体,该字体仅支持拉丁字符集。显而易见的解决方案是捆绑一个实际的日文 .TTF 字体并让 JFreeChart 使用它。这很好用,因为输出文本使用正确的字形并且布局也正确。
我的问题
java.awt 是如何在第一个场景中正确渲染日文字符的,当使用的源字体实际上不支持除拉丁字符之外的任何内容时?如果重要的话,我正在使用 JDK 1.7u45 在 OS X 10.9 上进行测试。
有没有办法在不捆绑单独的日文字体的情况下呈现日文字符?(这是我的最终目标!)虽然捆绑解决方案有效,但如果可以避免的话,我不想在我的应用程序中增加 6 Mb 的膨胀。即使没有字体(至少在我的本地环境中),Java 也清楚地知道如何以某种方式呈现日文字形——这似乎只是被破坏的指标。我想知道这是否与下面的“frankenfont”问题有关。
在 JRE 执行内部转换之后,为什么 Source Sans Pro 字体告诉调用者(通过canDisplayUpTo())它可以显示日语字符,即使它不能?(见下文。)
编辑澄清:
这是一个服务器应用程序,我们正在渲染的文本将显示在客户端的浏览器和/或 PDF 导出中。图表始终在服务器上栅格化为 PNG。
我无法控制服务器操作系统或环境,尽管使用 Java 标准平台字体会很好,但许多平台的字体选择很差,在我的用例中是不可接受的,所以我需要捆绑我自己的(在至少对于拉丁字体)。对日文文本使用平台字体是可以接受的。
该应用程序可能会被要求显示日语和拉丁语文本的混合,而无需事先了解文本类型。如果字符串包含混合语言,只要字形正确呈现,我对使用什么字体感到矛盾。
细节
我知道 java.awt.Font#TextLayout 很聪明,并且在尝试布局文本时,它首先询问底层字体是否可以实际呈现提供的字符。如果不是,它可能会换成一种知道如何渲染这些字符的不同字体,但这不会发生在这里,基于我对 JRE 类的调试很远。TextLayout#singleFont
总是为字体返回一个非空值,并通过fastInit()
构造函数的一部分进行。
一个非常奇怪的注意事项是,Source Sans Pro 字体以某种方式被强制告诉调用者它确实知道在 JRE 对字体执行转换后如何渲染日文字符。
例如:
// 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));
这个的输出是:
Font Source Sans Pro can display up to: 0
Font Source Sans Pro can display up to: -1
请注意,上面提到的三行“魔术操纵”不是我自己做的;我们将真正的源字体对象传递给 JFreeChart,但在绘制字形时它会被 JRE 修改,这就是上面三行“魔术操作”代码所复制的内容。上面显示的操作与以下调用序列中发生的操作在功能上等价:
- org.jfree.text.TextUtilities#drawRotatedString
- sun.java2d.SunGraphics2D#drawString
- java.awt.font.TextLayout#(构造函数)
- java.awt.font.TextLayout#singleFont
当我们在“魔术”操作的最后一行调用 Font.getFont() 时,我们仍然得到一个 Source Sans Pro 字体,但底层字体的font2D
字段与原始字体不同,这个单一字体现在声称它知道如何渲染整个字符串。为什么?看起来 Java 给了我们某种“frankenfont”,它知道如何渲染各种字形,即使它只理解底层源字体中提供的字形的度量。
此处是显示 JFreeChart 渲染示例的更完整示例,基于 JFreeChart 示例之一:https ://gist.github.com/sdudley/b710fd384e495e7f1439此示例的输出如下所示。