为什么只有拉丁字符的Java字体声称支持亚洲字符,尽管它不支持?
在使用JFreeChart呈现图表时,我注意到当图表的类别标签包含日文字符时出现了布局问题。虽然文本是使用正确的图示符呈现的,但文本定位在错误的位置,可能是因为字体度量错误 图表最初配置为使用该文本的字体,该字体仅支持拉丁字符集。显而易见的解决方案是捆绑一个实际的日文.TTF字体,并让JFreeChart使用它。这很好,因为输出文本使用了正确的图示符,并且布局也正确 我的问题为什么只有拉丁字符的Java字体声称支持亚洲字符,尽管它不支持?,java,fonts,awt,jfreechart,fontmetrics,Java,Fonts,Awt,Jfreechart,Fontmetrics,在使用JFreeChart呈现图表时,我注意到当图表的类别标签包含日文字符时出现了布局问题。虽然文本是使用正确的图示符呈现的,但文本定位在错误的位置,可能是因为字体度量错误 图表最初配置为使用该文本的字体,该字体仅支持拉丁字符集。显而易见的解决方案是捆绑一个实际的日文.TTF字体,并让JFreeChart使用它。这很好,因为输出文本使用了正确的图示符,并且布局也正确 我的问题 在第一个场景中,当使用的源字体实际上不支持除拉丁字符以外的任何字符时,java.awt是如何正确呈现日语字符的?如果有
- 在第一个场景中,当使用的源字体实际上不支持除拉丁字符以外的任何字符时,java.awt是如何正确呈现日语字符的?如果有必要的话,我正在使用JDK1.7u45在OSX10.9上进行测试
- 有没有办法在不捆绑单独的日文字体的情况下呈现日文字符?(这是我的最终目标!)虽然捆绑解决方案有效,但如果可以避免的话,我不想在我的应用程序中添加6MB的膨胀。Java清楚地知道如何以某种方式呈现日文字形,即使没有字体(至少在我的本地环境中是这样)——看起来只是一些指标被破坏了。我想知道这是否与下面的“法兰克福”问题有关
- 在JRE执行内部转换后,为什么源SAN Pro字体会告诉调用者(通过)它可以显示日语字符,尽管它不能?(见下文。)
- 这是一个服务器应用程序,我们呈现的文本将显示在客户端浏览器和/或PDF导出中。图表始终光栅化为服务器上的PNG
- 我无法控制服务器操作系统或环境,尽管使用Java标准平台字体会很好,但许多平台的字体选择很差,这在我的用例中是不可接受的,因此我需要捆绑自己的字体(至少对于拉丁字体)。日语文本可以使用平台字体
- 该应用程序可能会被要求显示日语和拉丁语的混合文本,而不需要事先知道文本类型。如果字符串包含混合语言,我对使用什么字体感到矛盾,只要字形正确呈现
TextLayout#singleFont
始终为字体返回非空值,并通过构造函数的fastInit()
部分进行操作
一个非常奇怪的注意事项是,在JRE对字体执行转换后,源Sans Pro字体以某种方式被迫告诉调用者它确实知道如何呈现日文字符
例如:
// 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));
注意,上面提到的三行“魔术操作”不是我自己做的;我们将真正的源字体对象传递给JFreeChart,但在绘制glyphs时,它会被JRE屏蔽,这就是上面三行“魔术操作”代码所复制的内容。上面显示的操作在功能上等同于以下调用序列中发生的操作:
font2D
字段与原始字体不同,并且这个单一字体现在声称它知道如何渲染整个字符串。为什么?Java似乎给了我们某种“frankenfont”,它知道如何呈现各种字形,尽管它只了解底层源字体中提供的字形的度量
这里有一个更完整的JFreeChart渲染示例,它基于一个JFreeChart示例:该示例的输出如下所示
源Sans Pro字体示例(布局不正确):
IPA日文字体示例(布局正确):
虽然它没有直接回答您的问题,但我认为它可能提供了一个有用的参考点,可以在未经修饰的图表中使用平台的默认字体显示结果。的简化版本如下所示 由于第三方字体度量的变幻莫测,我尽量避免偏离平台的标准,这些标准是根据平台支持的区域设置选择的。逻辑字体映射到平台中的物理字体。在Mac OS上,相关文件位于
$JAVA_HOME/jre/lib/
中,其中$JAVA_HOME
是评估/usr/libexec/JAVA_HOME-v1.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);
});
}
}
我终于明白了。有许多潜在的原因,这进一步受到跨平台变异剂量增加的阻碍 JFreeChart在错误的位置渲染文本,因为它使用了不同的字体对象 出现布局问题是因为JFreeChart无意中使用了与AWT实际用于呈现字体的字体对象不同的字体对象来计算布局的度量。(用于
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);
});
}
}
font = Font.getFont(font.getAttributes())