为什么只有拉丁字符的Java字体声称支持亚洲字符,即使它不支持?

Sco*_*ley 7 java fonts awt jfreechart fontmetrics

使用JFreeChart渲染图表时,我注意到当图表的类别标签包含日文字符时出现布局问题.虽然文本使用正确的字形进行渲染,但文本位于错误的位置,可能是因为字体指标错误.

该图表最初配置为对该文本使用Source Sans Pro Regular字体,该字体仅支持拉丁字符集.显而易见的解决方案是捆绑实际的日文.TTF字体并要求JFreeChart使用它.这很好用,因为输出文本使用正确的字形,并且它也正确布局.

我的问题

  • 在使用除拉丁字符之外实际上不支持任何内容的源字体时,java.awt如何在第一个场景中正确呈现日文字符?如果重要的话,我正在使用JDK 1.7u45在OS X 10.9上进行测试.

  • 有没有办法渲染日文字符而不捆绑单独的日文字体?(这是我的最终目标!)尽管捆绑解决方案有效,但如果可以避免,我不想在我的应用程序中添加6 Mb的膨胀.Java清楚地知道如何在没有字体的情况下以某种方式呈现日语字形(至少在我的本地环境中) - 它看起来只是被破坏的指标.我想知道这是否与下面的"frankenfont"问题有关.

  • 在JRE执行内部转换之后,为什么Source Sans Pro字体告诉调用者(通过canDisplayUpTo())它可以显示日文字符,即使它不能?(见下文.)

编辑澄清:

  • 这是一个服务器应用程序,我们呈现的文本将显示在客户端的浏览器和/或PDF导出中.图表始终光栅化为服务器上的PNG.

  • 我无法控制服务器操作系统或环境,并且使用Java标准平台字体一样好,许多平台的字体选择都很差,在我的用例中是不可接受的,所以我需要捆绑自己的(在至少对于拉丁字体).可以使用日语文本的平台字体.

  • 可能会要求应用程序显示日语和拉丁文本的混合,而不会有文本类型的先验知识.如果字符串包含混合语言,只要字形正确呈现,我就会使用什么字体变得矛盾.

细节

我知道java.awt.Font #TextLayout是聪明的,并且在尝试布局文本时,它首先询问底层字体是否可以实际呈现所提供的字符.如果没有,它可能会以不同的字体交换,知道如何呈现这些字符,但这不会发生在这里,基于我对JRE类的调试.TextLayout#singleFont始终返回字体的非空值,它继续通过fastInit()构造函数的一部分.

一个非常好奇的注意的是,源三世Pro字体莫名其妙地被裹挟进告诉它调用者知道如何呈现日文字符的JRE执行上的字体转换之后.

例如:

// 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));
Run Code Online (Sandbox Code Playgroud)

这个输出是:

Font Source Sans Pro can display up to: 0
Font Source Sans Pro can display up to: -1
Run Code Online (Sandbox Code Playgroud)

请注意,上面提到的三条"魔法操纵"并不是我自己所做的; 我们将真正的源字体对象传递给JFreeChart,但是在绘制字形时它会被JRE传送,这就是上面三行"魔术操作"代码复制的内容.上面显示的操作功能等同于以下调用序列中发生的操作:

  1. org.jfree.text.TextUtilities#drawRotatedString
  2. sun.java2d.SunGraphics2D drawString之#
  3. java.awt.font.TextLayout中的#(构造函数)
  4. java.awt.font.TextLayout中的#singleFont

当我们在"魔术"操作的最后一行调用Font.getFont()时,我们仍然得到Source Sans Pro字体,但是底层字体的font2D字段与原始字体不同,而这个单字体现在声称它知道它如何渲染整个字符串.为什么?似乎Java正在向我们提供某种"frankenfont",它知道如何呈现各种字形,即使它只了解底层源字体中提供的字形的度量.

这里显示了一个更完整的示例,显示了JFreeChart呈现示例,基于JFreeChart示例之一:https://gist.github.com/sdudley/b710fd384e495e7f1439此示例的输出如下所示.

Source Sans Pro字体的示例(布局不正确):

在此输入图像描述

IPA日语字体示例(正确布局):

在此输入图像描述

Sco*_*ley 5

我终于弄明白了.有许多潜在原因,跨越平台变异的额外剂量进一步阻碍了这些原因.

JFreeChart在错误的位置呈现文本,因为它使用不同的字体对象

出现布局问题是因为JFreeChart无意中使用与AWT实际用于渲染字体的Font对象不同的Font对象来计算布局的度量.(作为参考,JFreeChart的计算发生在org.jfree.text#getTextBounds.)

不同Font对象的原因是问题中提到的隐含"魔术操纵"的结果,这是在内部执行的java.awt.font.TextLayout#singleFont.

这三行魔术操作可以简化为:

font = Font.getFont(font.getAttributes())
Run Code Online (Sandbox Code Playgroud)

在英语中,这要求字体管理器根据提供的字体的"属性"(名称,族,点大小等)为我们提供一个新的Font对象.在某些情况下,Font它给你的回报将与Font你最初开始时不同.

要更正指标(从而修复布局),修复方法是Font在JFreeChart对象中设置字体之前在您自己的对象上运行上面的单行.

在这样做之后,布局对我来说很好,就像日文字符一样.它应该解决的布局对你太,尽管它可能无法正确显示日文字符为.阅读以下有关原生字体的内容,了解原因

即使您为其提供物理TTF文件,Mac OS X字体管理器也更喜欢返回本机字体

文本的布局由上述变化确定......但为什么会发生这种情况?在什么情况下FontManager实际上会给我们一个不同于Font我们提供的对象类型的对象?

原因有很多,但至少在Mac OS X上,与问题相关的原因是字体管理器似乎更愿意尽可能地返回本机字体.

换句话说,如果您使用名为"Foobar"的物理TTF字体创建新字体Font.createFont,然后使用从"Foobar"物理字体派生的属性调用Font.getFont()...只要OS X已经有安装Foobar字体后,字体管理器会返回一个CFont对象而不是TrueTypeFont您期望的对象.这似乎是正确的,即使你注册的字体通过GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont.

在我的情况下,这引发了一个红色鲱鱼进入调查:我已经在我的Mac上安装了"Source Sans"字体,这意味着我得到的结果不同于那些没有的人.

Mac OS X本机字体始终支持亚洲字符

问题的关键是Mac OS X CFont对象始终支持亚洲字符集.我不清楚允许这个的确切机制,但我怀疑它是OS X本身而不是Java的某种后备字体功能.在任何一种情况下,CFont总是声称(并且确实能够)使用正确的字形呈现亚洲字符.

这清楚地表明了允许原始问题发生的机制:

  • 我们Font从物理TTF文件创建了一个物理,它本身不支持日语.
  • 我的Mac OS X字体书中也安装了与上面相同的物理字体
  • 在计算图表的布局时,JFreeChart向物理Font对象询问日文文本的度量.物理Font无法正确执行此操作,因为它不支持亚洲字符集.
  • 当实际绘制图表时,魔术操作TextLayout#singleFont导致它获取一个CFont对象并使用相同命名的本机字体绘制字形,而不是物理字体TrueTypeFont.因此,字形是正确的,但它们没有正确定位.

根据您是否注册了字体以及是否在操作系统中安装了字体,您将得到不同的结果

如果Font.getFont()使用创建的TTF字体中的属性调用,则将获得三种不同结果中的一种,具体取决于字体是否已注册以及是否具有本机安装的相同字体:

  • 如果您确实安装了与TTF字体同名的本机平台字体(无论您是否注册了字体),您将获得CFont所需字体的亚洲支持.
  • 如果您Font在GraphicsEnvironment中注册了TTF 但没有相同名称的本机字体,则调用Font.getFont()将返回一个物理TrueTypeFont对象.这为您提供了所需的字体,但是您没有获得亚洲字符.
  • 如果您没有注册TTF Font并且您也没有相同名称的本机字体,则调用Font.getFont()将返回支持亚洲语的CFont,但它不是您请求的字体.

事后来看,这一切都不足为奇.导致:

我无意中使用了错误的字体

在生产应用程序中,我正在创建一个字体,但我忘了最初使用GraphicsEnvironment注册它.如果在执行上述魔术操作时尚未注册字体,Font.getFont()则不知道如何检索它,而是获得备份字体.哎呀.

在Windows,Mac和Linux上,这种备份字体通常似乎是Dialog,它是一种支持亚洲字符的逻辑(复合)字体.至少在Java 7u72中,Dialog字体默认为西方字母的以下字体:

  • Mac:Lucida Grande
  • Linux(CentOS):Lucida Sans
  • Windows:Arial

这个错误对于我们的亚洲用户来说实际上是一件好事,因为它意味着他们的字符集以逻辑字体呈现为预期......尽管西方用户没有得到我们想要的字符集.

由于它使用错误的字体进行渲染,我们无论如何都需要修复日语布局,因此我决定在未来版本中尝试标准化一种常用字体(因此更接近trashgod的建议).

此外,该应用程序具有字体呈现质量要求,可能并不总是允许使用某些字体,因此一个合理的决定似乎是尝试配置应用程序使用Lucida Sans,这是Oracle包含的一种物理字体所有Java副本.但...

Lucida Sans在所有平台上都不能与亚洲角色搭配得很好

尝试使用Lucida Sans的决定似乎是合理的......但我很快发现Lucida Sans的处理方式存在平台差异.在Linux和Windows上,如果要求提供"Lucida Sans"字体的副本,则会获得物理TrueTypeFont对象.但该字体不支持亚洲字符.

如果你要求"Lucida Sans",那么同样的问题在Mac OS X上也是如此......但是如果你要求稍微不同的名字"LucidaSans"(注意缺少空间),那么你得到一个CFont支持Lucida Sans 的对象作为亚洲人物,所以你可以吃蛋糕,也可以吃.

在其他平台上,请求"LucidaSans"会生成标准Dialog字体的副本,因为没有这样的字体,而Java正在返回其默认值.在Linux上,你在这里有点幸运,因为Dialog实际上默认使用Lucida Sans来获取西方文本(并且它也为亚洲字符使用了一个不错的后备字体).

这为我们提供了一条路径,通过请求具有以下名称的字体,在所有平台上获得(几乎)相同的物理字体,并且还支持亚洲字符:

  • Mac OS X:"LucidaSans"(产生Lucida Sans +亚洲备份字体)
  • Linux:"Dialog"(产生Lucida Sans +亚洲备份字体)
  • Windows:"Dialog"(产生Arial +亚洲备份字体)

我已经仔细研究了Windows上的fonts.properties,我找不到默认为Lucida Sans的字体序列,所以看起来我们的Windows用户需要卡住Arial ...但至少它不是那么视觉上不同来自Lucida Sans,以及Windows字体的渲染质量是合理的.

一切都在哪里结束?

总而言之,我们现在几乎只使用平台字体.(我确信@trashgod现在笑得很开心!)Mac和Linux服务器都获得Lucida Sans,Windows获得Arial,渲染质量很好,每个人都很开心!