3

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

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

GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);

所以我的问题是——

  1. 为什么即使我从文件创建字体,我也必须将字体注册到 JVM 中?这是否意味着 JFreeChart 不使用我直接设置的字体?

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

我知道我可以创建一个fallback目录$JAVA_HOME/jre/lib并将我的字体放入其中。但这并不能解释为什么 JFreeChart 不能用我设置的字体显示。

更新

我很确定字体已正确加载,registerFont()当我将程序部署到 Tomcat 时也返回 true。

更新 2

根据JAVA 2D FAQ,现在我意识到我必须调用registerFont()才能将我自己的字体“安装”到 JVM 中,并且我的字体将通过Font构造函数可用。

从 Java SE 6 开始,有一个方法:GraphicsEnvironment.registerFont(),它使您能够使“创建的”字体可用于 Font 构造函数并通过 Font 枚举 API 列出。Font.createFont() 和这个方法结合起来提供了一种将字体“安装”到正在运行的 JRE 中的方法,因此它就像 O/S 安装的字体一样可用。但是,此字体不会在 JRE 调用中持续存在。

但是,既然我已经Font创建/派生了实例createFont(),为什么我的程序还不需要创建其他的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;
  }
}
4

3 回答 3

4

当您直接从 TTF 创建一个时Font,Java 显然知道从该单个 Font 对象中获取字体文件本身的副本的位置。那么为什么字体也需要注册才能使用呢?答案是它并不总是必须被注册,或者至少只要整个控制链Font直接使用原始对象就可以了。

当 Java 尝试渲染字体时,到底发生了什么?

细微差别在于 JFreeChart 要求呈现文本的方式。文本的渲染是在TextUtilities#drawRotatedStringjcommon 的方法中执行的。在 JDK7 上,默认情况下,此方法将:

  • AttributedString根据您传入的字体的“属性”创建一个,
  • 调用Graphics2D#drawString属性字符串,然后
  • 创建一个新TextLayout对象。

TextLayout是选择要提供给的实际 Font 对象的类Graphics2DTextLayout旨在支持使用各种字体渲染多语言文本(即使同一个源字符串需要以多种字体渲染),通过使用自动字体选择来为每段字符串找到合适的字体。

上面提到的“属性”是关于字体的简单事实(如字体系列名称、大小等),它们源自Font您提供的字体。如果您提供的字体无法呈现输入字符串中的所有字符,则这些属性用于选择相似的字体以用于需要使用不同字体的文本运行。

当 JFreeChart 调用 TextLayout 时,它总是这样运行:

  • Font从您提供的对象中提取属性,
  • 调用static Font#getFont以获取与提供的属性匹配的字体(请参阅 参考资料TextLayout#singleFont),以及
  • 使用返回的(可能不同的)Font 对象来绘制文本。

如果你没有在某处静态GraphicsEnvironment#registerFont注册你的字体(比如使用),那么静态 Font#getFont方法所要做的就是包含字体系列名称的属性字符串。它不知道在哪里访问包含对 TTF 的引用的 Font 对象,更不用说实际呈现字体所需的任何数据了。

好的,但我以为你说我不需要注册字体?

如果您不想注册字体,那么诀窍在于确保您的文本仅使用Font您提供的对象呈现。碰巧有另一个构造函数直接TextLayout接受一个Font对象,而不是一组用于查找字体的属性。

有用的是,JFreeChart 甚至提供了一种强制它使用此构造函数的方法。在TextUtilities#drawRotatedString中,可以使用一个特殊的配置参数来强制 JFreeChart使用您提供TextLayout的确切对象来构造对象本身。Font

为此,您可以像这样设置 jcommon.properties 文件:

  • 创建一个名为的资源文件jcommon.properties(最终应位于类路径/JAR 的根级别),并且
  • 添加以下行:

org.jfree.text.UseDrawRotatedStringWorkaround=true

或者简单地调用静态函数:

TextUtilities.setUseDrawRotatedStringWorkaround(true)

这将要求 JFreeChart 直接使用您的字体呈现文本,并且...瞧!即使没有注册字体,它也可以工作。这是在上述问题的上下文中测试的,即使用 JFreeChart 将文本直接呈现为光栅图像。如果您尝试渲染到显示设备(我没有尝试过),您的里程可能会有所不同。

不注册字体是否明智?

我不能肯定地说。我的一个应用程序在 OSGi 容器中运行,我担心通过静态注册永远无法取消注册的字体来创建 PermGen 类加载器泄漏。直接使用 Font 对象可以避免这个问题,这就是我想走这条路的原因。我想如果你这样做,一个特定的 Java 平台总是可能会遇到问题,但这在我的测试中运行良好,至少在 Windows、Linux 和 OS X 主机上使用 Oracle JDK 7。

于 2014-10-03T01:54:27.357 回答
1

为什么即使我从文件创建字体,我也必须将字体注册到 JVM 中?

JVM 怎么知道你的字体存在?

您的字体必须在 JVM 中注册,以便 Java 知道如何在 JFreeChart 用于呈现图表的图形环境中绘制您的字体。

如何让它始终使用我设置的字体以便在所有环境中正确显示字符?

您需要检查该registerFont()方法是否返回 true。如果它返回 false,则您的字体不可用。

看起来您正在正确加载字体。也许您的字体的文件路径在您的服务器上不正确。你可能想试试

getClass().getResource(fontPath);
于 2012-08-24T17:05:03.043 回答
1

我知道这是一个老问题,但我自己正在寻找答案并看到上面的回复,我仍然没有真正理解注册字体的目的。研究了这个问题后,我发现了这个问题:

您不必在图形环境中注册字体,但这样做的好处是能够在“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));
于 2017-07-29T20:12:08.183 回答