使用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
始终返回字体的非null值,并通过构造函数的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传送,这就是上面三行“魔术操作”代码复制的内容。上面显示的操作功能等同于以下调用序列中发生的操作:
当我们在“魔术”操作的最后一行调用Font.getFont()时,我们仍然会返回Source Sans Pro字体,但底层字体的font2D
字段与原始字体不同,并且这个单一字体现在声称它知道如何呈现整个字符串。为什么?似乎Java正在向我们提供某种“frankenfont”,它知道如何渲染各种字形,即使它只能理解底层源字体中提供的字形的度量。
此处显示JFreeChart渲染示例的更完整示例,基于JFreeChart示例之一:https://gist.github.com/sdudley/b710fd384e495e7f1439此示例的输出如下所示。
答案 0 :(得分:3)
虽然它没有直接解决您的问题,但我认为它可以提供一个有用的参考点,以使用平台图表中的平台默认字体显示结果。简体版BarChartDemo1
,source如下所示。
由于第三方字体指标的变幻莫测,我尽量避免偏离平台的标准logical fonts,这是根据平台支持的区域设置选择的。逻辑字体映射到平台的configuration files中的物理字体。在Mac OS上,相关文件位于$JAVA_HOME/jre/lib/
,其中$JAVA_HOME
是评估/usr/libexec/java_home -v 1.n
的结果, n 是您的版本。我在版本7或8中看到了类似的结果。特别是,fontconfig.properties.src
定义了用于提供日语字体系列变体的字体。所有映射似乎都使用MS Mincho
或MS Gothic
。
import java.awt.Dimension;
import java.awt.EventQueue;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.CategoryDataset;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.ApplicationFrame;
import org.jfree.ui.RefineryUtilities;
/**
* @see http://stackoverflow.com/a/26090878/230513
* @see http://www.jfree.org/jfreechart/api/javadoc/src-html/org/jfree/chart/demo/BarChartDemo1.html
*/
public class BarChartDemo1 extends ApplicationFrame {
/**
* Creates a new demo instance.
*
* @param title the frame title.
*/
public BarChartDemo1(String title) {
super(title);
CategoryDataset dataset = createDataset();
JFreeChart chart = createChart(dataset);
ChartPanel chartPanel = new ChartPanel(chart){
@Override
public Dimension getPreferredSize() {
return new Dimension(600, 400);
}
};
chartPanel.setFillZoomRectangle(true);
chartPanel.setMouseWheelEnabled(true);
setContentPane(chartPanel);
}
/**
* Returns a sample dataset.
*
* @return The dataset.
*/
private static CategoryDataset createDataset() {
// row keys...
String series1 = "First";
String series2 = "Second";
String series3 = "Third";
// column keys...
String category1 = "クローズ";
String category2 = "クローズ";
String category3 = "クローズクローズクローズ";
String category4 = "Category 4 クローズ";
String category5 = "Category 5";
// create the dataset...
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
dataset.addValue(1.0, series1, category1);
dataset.addValue(4.0, series1, category2);
dataset.addValue(3.0, series1, category3);
dataset.addValue(5.0, series1, category4);
dataset.addValue(5.0, series1, category5);
dataset.addValue(5.0, series2, category1);
dataset.addValue(7.0, series2, category2);
dataset.addValue(6.0, series2, category3);
dataset.addValue(8.0, series2, category4);
dataset.addValue(4.0, series2, category5);
dataset.addValue(4.0, series3, category1);
dataset.addValue(3.0, series3, category2);
dataset.addValue(2.0, series3, category3);
dataset.addValue(3.0, series3, category4);
dataset.addValue(6.0, series3, category5);
return dataset;
}
/**
* Creates a sample chart.
*
* @param dataset the dataset.
*
* @return The chart.
*/
private static JFreeChart createChart(CategoryDataset dataset) {
// create the chart...
JFreeChart chart = ChartFactory.createBarChart(
"Bar Chart Demo 1", // chart title
"Category", // domain axis label
"Value", // range axis label
dataset, // data
PlotOrientation.HORIZONTAL, // orientation
true, // include legend
true, // tooltips?
false // URLs?
);
return chart;
}
/**
* Starting point for the demonstration application.
*
* @param args ignored.
*/
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
BarChartDemo1 demo = new BarChartDemo1("Bar Chart Demo 1");
demo.pack();
RefineryUtilities.centerFrameOnScreen(demo);
demo.setVisible(true);
});
}
}