为什么我必须调用GraphicsEnvorinment.registerFont(),即使我的文件是从文件创建的?
我正在开发一个使用JFreeChart渲染图表的Web应用程序。 但是,当服务器没有安装任何中文字体时,即使我设置了字体,JFreeChart也不会显示中文字符。
然后我写了一个小的测试代码,发现在绘制图表之前添加这行代码可以解决问题。
GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);
所以我的问题是 –
-
即使我从File创建我的字体,为什么我必须将字体注册到JVM? 意味着JFreeChart的剂量不使用我直接设置的字体?
-
当我将程序部署到服务器时,即使我添加了这行代码,它也不会显示中文字符。 如何使它始终使用我设置的字体,以便在所有环境中正确显示字符?
我知道我可以在$JAVA_HOME/jre/lib
创建一个fallback
目录并将我的字体放入其中。 但这并不能解释为什么JFreeChart无法显示我设置的字体。
UPDATE
我很确定字体加载正确,因此当我将程序部署到Tomcat时,剂量registerFont()
返回true。
更新2
根据JAVA 2D FAQ ,现在我意识到我必须调用registerFont()
才能将我自己的字体“安装”到JVM中,我的字体将通过Font
构造函数提供。
从Java SE 6开始,有一个方法:GraphicsEnvironment.registerFont(),它使您能够为Font构造函数提供“已创建”字体,并通过Font枚举API列出。 Font.createFont()和这个方法相结合,提供了一种方法,将Font“安装”到正在运行的JRE中,因此它就像O / S安装的字体一样可用。 但是,此字体不会在JRE调用中持续存在。
但是,由于我已经创建/派生自createFont()
Font
实例,为什么选择我的程序仍需要创建其他Font
?
以下是我使用的代码,它只是输出PNG格式的图表。 如果你想运行代码,你应该改变输出位置和字体以满足你的需要, 这里是我在代码中使用的中文字体的SourceForge链接 。
import java.awt.Font; import java.awt.GraphicsEnvironment; import java.io.File; import org.jfree.chart.ChartFactory; import org.jfree.chart.ChartUtilities; import org.jfree.chart.JFreeChart; import org.jfree.chart.StandardChartTheme; import org.jfree.data.general.DefaultPieDataset; import org.jfree.data.general.PieDataset; public class Problem { public static void main(String[] args) throws Exception { setJFreeChartTheme(); PieDataset dataset = createDataSet(); JFreeChart chart = ChartFactory.createPieChart( "Chinese Testing", dataset, true, true, false); ChartUtilities.saveChartAsJPEG(new File("/tmp/output.png"), chart, 800, 600); System.out.println("Done"); } private static void setJFreeChartTheme() throws Exception { Font font = loadFont(); //================================================================== GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); //================================================================== StandardChartTheme theme = new StandardChartTheme("Chinese font", true); theme.setExtraLargeFont(font.deriveFont(Font.BOLD, 20)); theme.setLargeFont(font.deriveFont(Font.BOLD, 16)); theme.setRegularFont(font.deriveFont(Font.PLAIN, 14)); theme.setSmallFont(font.deriveFont(Font.PLAIN, 12)); ChartFactory.setChartTheme(theme); } private static Font loadFont() throws Exception { File file = new File("/tmp/wqy-zenhei.ttc"); return Font.createFont(Font.TRUETYPE_FONT, file); } private static PieDataset createDataSet() { DefaultPieDataset dataset = new DefaultPieDataset(); dataset.setValue("種類1", Integer.valueOf(1)); dataset.setValue("種類2", Integer.valueOf(2)); dataset.setValue("種類3", Integer.valueOf(3)); return dataset; } }
当你直接从TTF创建一个Font
,Java显然知道从哪个Font对象中获取字体文件本身的副本。 那么为什么字体也需要注册才能使用呢? 答案是它并不总是必须注册 ,或者至少不是只要整个控制链直接使用原始的Font
对象。
当Java尝试渲染字体时会发生什么?
细微差别在于JFreeChart如何呈现文本。 在TextUtilities#drawRotatedString
的TextUtilities#drawRotatedString
方法中执行文本渲染。 在JDK7上,默认情况下此方法:
- 根据您传入的字体的“属性”创建一个
AttributedString
, - 在属性字符串上调用
Graphics2D#drawString
,然后 - 创建一个新的
TextLayout
对象。
TextLayout
是选择要提供给Graphics2D
的实际Font对象的类。 TextLayout
旨在支持使用各种字体呈现多语言文本(即使相同的单个源字符串需要以多种字体呈现),方法是使用自动字体选择为每个字符串找到合适的字体。
上面提到的“属性”是关于从您提供的Font
派生的字体(如字体系列名称,大小等)的简单事实。 如果您提供的字体无法呈现输入字符串中的所有字符,则使用这些属性选择类似的字体,以用于需要使用不同字体的文本运行。
当JFreeChart调用TextLayout时,它总是这样运行:
- 从您提供的
Font
对象中提取属性, - 调用静态
Font#getFont
来获取与提供的属性匹配的字体(参见TextLayout#singleFont
),以及 - 使用返回的(可能不同的)Font对象来绘制文本。
如果你没有在某个地方静态注册你的字体(比如GraphicsEnvironment#registerFont
),那么静态 Font#getFont
方法必须继续的是包含字体系列名称的属性字符串。 它不知道在何处访问包含对TTF的引用的Font对象,更不用说实际呈现字体所需的任何数据。
好的,但我以为你说我不需要注册字体?
如果您不想注册字体,那么诀窍在于确保仅使用您提供的Font
对象呈现文本。 碰巧的是, TextLayout
另一个构造函数直接接受Font
对象,而不是用于查找字体的一组属性。
有帮助的是,JFreeChart甚至提供了一种方法来强制它使用这个构造函数。 在TextUtilities#drawRotatedString
,可以使用特殊配置参数强制JFreeChart使用您提供的确切Font
对象来构造TextLayout
对象。
为此,您可以设置如下的jcommon.properties文件:
- 创建一个名为
jcommon.properties
的资源文件(最终应该在类路径/ JAR的根级别),并且 - 添加以下行:
org.jfree.text.UseDrawRotatedStringWorkaround=true
或者只是调用静态函数:
TextUtilities.setUseDrawRotatedStringWorkaround(true)
这将要求JFreeChart直接使用您的字体呈现文本,并且…… 瞧! 即使没有注册字体也可以工作 。 这是在上述问题的上下文中测试的,即使用JFreeChart将文本直接渲染为光栅图像。 如果您尝试渲染到显示设备(我没试过),您的里程可能会有所不同。
注册字体是明智的吗?
我不能肯定地说。 我的一个应用程序在OSGi容器内运行,我担心通过静态注册永远不能注册的Font来创建PermGen类加载器泄漏。 直接使用Font对象避免了这个问题,这就是我想要走这条路的原因。 我认为,如果你这样做,特定的Java平台可能会遇到麻烦,但是在我的测试中,至少在Windows,Linux和带有Oracle JDK 7的OS X主机上运行良好。
即使我从文件创建我的字体,为什么我必须将字体注册到JVM?
JVM怎么会知道你的字体存在?
您的字体必须在JVM中注册,以便Java知道如何在JFreeChart用于呈现图表的图形环境中绘制字体。
如何使它始终使用我设置的字体,以便在所有环境中正确显示字符?
您需要检查registerFont()
方法是否返回true。 如果返回false,则表示您的字体不可用。
看起来你正在正确加载字体。 可能您的服务器上的字体文件路径不正确。 你可能想试试
getClass().getResource(fontPath);
我知道这是一个老问题,但我自己也在寻找答案并看到上面的回答,我仍然不明白注册字体的目的。 研究过这个问题后,我发现了这个问题:
您不必在图形环境中注册字体,但这样做的好处是可以使用“new Font()”构造函数中的注册字体。
您可以使用以下代码获取当前可用的所有字体列表(即已安装并可在您的应用程序中使用):
String fonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
假设您使用的是Windows,其中一个已安装的字体是Arial,您可以在应用程序中使用此字体,如下所示:
JButton yesButton = new JButton ("Yes"); yesButton.setFont(new Font("Arial", Font.PLAIN,30));
现在假设您要加载并使用文件中自己的自定义字体:
Font robotoFont = Font.createFont(Font.TRUETYPE_FONT,getClass().getResourceAsStream("/res/fonts/Roboto/Roboto-Light.ttf"));
如果您想将其设置为JButton的字体,您可以编写以下代码:
JButton yesButton = new JButton("Yes"); yesButton.setFont(robotoFont.deriveFont(Font.PLAIN, 30f));
但是,如果您尝试编写一些代码,例如:
JButton yesButton = new JButton("Yes"); yesButton.setFont(new Font ("Roboto Light", Font.PLAIN,30));
JButton只会被赋予默认字体,因为图形环境不知道任何名为“Roboto Light”的字体。 解决方案是在图形环境中注册字体 :
GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); genv.registerFont(robotoFont);
然后,您将能够在’new Font()’构造函数中使用此字体,如下所示:
JButton yesButton = new JButton("Yes"); bestButton.setFont(new Font ("Roboto Light", Font.PLAIN,30));