当前位置: 首页 > 知识库问答 >
问题:

为什么我必须调用图形引用.registerFont() 即使我的字体是从文件创建的?

吴正祥
2023-03-14

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

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

GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont(font);

所以我的问题是-

>

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

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

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

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

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

    从Java SE 6开始,有一个方法:graphics environment . register Font(),它使您能够将“创建的”字体提供给字体构造器,并通过字体枚举API列出。Font.createFont()和这个方法结合起来提供了一种将字体“安装”到正在运行的JRE中的方法,因此它就像操作系统中安装的字体一样可用。然而,这种字体在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;
      }
    }
    
  • 共有3个答案

    钱照
    2023-03-14

    我知道这是一个老问题,但我自己也在寻找答案,看到上面的回复后,我仍然不明白注册字体的目的。研究了这个问题后,我发现:

    您不必在图形环境中注册您的字体,但是这样做的好处是可以在“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));
    
    司徒兴思
    2023-03-14

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

    JVM还能如何知道您的字体存在?

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

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

    您需要检查Register sterFont()方法是否返回true。如果返回false,则表示您的字体不可用。

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

    getClass().getResource(fontPath);
    
    蒙弘图
    2023-03-14

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

    细微的差别在于JFreeChart要求如何呈现文本。文本的呈现是在jcommon的< code > text utilities # drawRotatedString 方法中执行的。在JDK7上,默认情况下,该方法将:

    • 根据您传入的字体的“属性”创建一个At⃣tedString
    • 在属性字符串上调用Graphics2D#draString,然后
    • 创建一个新的TextLayout对象。

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

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

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

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

    如果您没有在某个地方静态注册字体(例如使用GraphicsEnvironment#registerFont),那么静态font#getFont方法必须继续执行的就是包含字体系列名称的属性字符串。它不知道在哪里访问包含对TTF的引用的Font对象,更不用说实际渲染字体所需的任何数据了。

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

    有用的是,JFreeChart甚至提供了一种方法来强制它改用这个构造函数。在“文本实用程序#绘制旋转字符串”中,可以使用一个特殊的配置参数来强制 JFreeChart 使用您提供的确切字体对象构造文本布局对象本身。

    为此,您可以建立一个jcommon.properties文件,如下所示:

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

    < code > org . jfree . text . usedrawrotatedstringworkrange = true

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

    TextUtilities.setUseDrawRotatedStringWorkaround(true)
    

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

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

     类似资料:
    • 关于在最新版本的GCC和Clang中编译有几个问题:实验::filessystems链接器错误 但是现在< code>filesystem已经被c 17接受,所以不再需要< code>experimental或< code>-lstdc fs标志,对吗? 错了,我甚至不能 只给了我< code >实验版本,我怎么能包括正式接受的版本呢?

    • 问题内容: 我创建了一个用于显示工具提示的指令: 对应功能: 应用于此: 这是我观点的一部分,由拥有者的控制器处理 为什么必须调用才能将更改应用到,该更改是早先声明和初始化的? 问题答案: 因为附加到事件的回调超出了angular的范围;angular不知道该函数何时运行/结束,因此摘要循环永远不会运行。 调用或告诉angular更新绑定并触发任何手表。

    • 我甚至不确定这段代码是否能做任何事情,即使它有效,但我不知道该怎么做才能摆脱“从内部类引用的局部变量必须是最终的或有效的最终”错误消息,该错误消息显示在以“fireballRight[i]”开头的三行上。 任何指导将不胜感激,谷歌似乎并没有帮助我。

    • C++20概念的一个特点是,在某些情况下,您必须编写。例如,[expr.prim.req]/3中的这个示例:

    • 我有方法 在正文中,我编写了一些HashMap的键集 看起来还可以,但如果我想重复这个代码 我得用try/catch来包围它。像这样 我不明白为什么

    • 问题内容: 我使用以下Dockerfile创建了一个Docker容器(已截断): 等等。 所有这些都可以,但是我的问题是软件包的安装方式/位置。 如果我仅使用rvm运行rvm,则会显示“无法找到rvm”,但是如果运行,它会起作用。(我在网上找到了“ -l -c”选项,但不知道它们的作用,也找不到令人满意的解释!) 这不是一个docker问题-这是一个bash / * nix问题-我认为存在一些关于