为什么我必须调用GraphicsEnvorinment.registerFont(),即使我的文件是从文件创建的?

我正在开发一个使用JFreeChart渲染图表的Web应用程序。 但是,当服务器没有安装任何中文字体时,即使我设置了字体,JFreeChart也不会显示中文字符。

然后我写了一个小的测试代码,发现在绘制图表之前添加这行代码可以解决问题。

GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font); 

所以我的问题是 –

  1. 即使我从File创建我的字体,为什么我必须将字体注册到JVM? 意味着JFreeChart的剂量不使用我直接设置的字体?

  2. 当我将程序部署到服务器时,即使我添加了这行代码,它也不会显示中文字符。 如何使它始终使用我设置的字体,以便在所有环境中正确显示字符?

我知道我可以在$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#drawRotatedStringTextUtilities#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));