为什么拉丁字符只字体的Java声称支持亚洲字符,即使它不? [英] Why does a Latin-characters-only Java font claim to support Asian characters, even though it does not?

查看:535
本文介绍了为什么拉丁字符只字体的Java声称支持亚洲字符,即使它不?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在渲染使用JFreeChart图表,我注意到一个布局问题时,图表的类别标签包含日文字符。虽然文本与正确的字形呈现,文本被放置在错误的位置,presumably因为字体规格是错误的。

图表最初配置为使用来源三世临定期字体的文本,只支持拉丁字符集。显而易见的解决方案是捆绑实际日语.TTF字体和询问的JFreeChart使用它。这工作得很好,在输出文本使用正确的字形,它也奠定了正常。

我的提问


  • java.awt中是如何结束在第一种情况下,正确地显示日文字符使用实际上并不支持除拉丁字符的任何一个源字体是什么时候?如果它的事项,我测试在OS X 10.9与JDK 1.7u45。


  • 有什么办法来渲染日文字符没有捆绑单独的日文字体? (这是我的最终目标!)虽然捆绑解决方案的工作,我不想膨胀的6 MB添加到我的应用程序,如果能够避免它。 Java的清楚的知道如何以某种方式使日本字形即使没有字体(至少在我的本地环境) - 它似乎只是被捣毁的度量标准。我想知道如果这是关系到下面的frankenfont的问题。


  • 在JRE执行内部改造后,为什么来源三世Pro字体告诉来电者(通过的 canDisplayUpTo()),它可以显示日文字符,即使它不能? (见下文)。


编辑澄清:


  • 这是一个服务器应用程序,我们正在绘制文本将在客户端的浏览器和/或PDF出口出现。该图表始终光栅化到PNG图片在服务器上。


  • 我已经在服务器操作系统或环境无法控制,并且很好的,因为它是使用Java标准平台的字体,许多平台都很差字体的选择,在我使用的情况下不能接受的,所以我需要捆绑我自己的(至少在拉丁字体)。使用日本文本的平台字体是可以接受的。


  • 该应用程序可能会被要求显示日文和拉丁文字的组合,没有任何先验文本类型的知识。我矛盾什么字体习惯,如果一个字符串包含混合的语言,只要字形正确呈现。


详细信息

据我了解,java.awt.Font中的#TextLayout是聪明的,而且,试图对文本进行布局时,首先询问的基本字体,他们是否能够真正使所提供的人物。如果不是,美元是如何呈现这些字符不同的字体p $ psumably掉期,但这不是在这里发生的,根据我的调试pretty遥远的JRE类。 的TextLayout#singleFont 总是返回一个非空值的字体,它通过一组对应的 fastInit()部分构造函数。

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

例如:

  //我们(在这个问题从第一个链接下载以上)这里载入我们的字体文件fontFile =新的文件(/ tmp目录/源的SAN-pro.regular.ttf);
字体字型= Font.createFont(Font.TRUETYPE_FONT,新的FileInputStream(fontFile));
。GraphicsEnvironment.getLocalGraphicsEnvironment()registerFont(字体);//这是我们希望显示一些日语文本
字符串str =クローズ//应该说该字体无法显示这些字符(返回code = 0)的System.out.println(字体+ font.getName()+最多可以显示:+ font.canDisplayUpTo(STR));//但是这样神奇的操作后,字体声称,它可以显示
//整个字符串(返回code = -1)AttributedString为=新AttributedString(STR,font.getAttributes());
地图< AttributedCharacterIterator.Attribute,对象>属性= as.getIterator()的getAttributes()。
字体newFont = Font.getFont(属性);// Eeek,-1!
的System.out.println(字体+ newFont.getName()+最多可以显示:+ newFont.canDisplayUpTo(STR));

这个输出是:

 字体来源三世临最多可显示:0
字体来源三世临最多可显示:-1

注意三行上面提到的魔术手法是不是我自己做东西;我们通过在真实来源Font对象JFreeChart的,但它得到了JRE绘制字形的时候,这是被改写的什么三线魔法操纵code以上重复的。上面所示的操作是在呼叫的下列顺序会发生什么情况的功能等同物:


  1. org.jfree.text.TextUtilities#drawRotatedString

  2. sun.java2d.SunGraphics2D drawString之#

  3. java.awt.font.TextLayout中的#(构造)

  4. java.awt.font.TextLayout中的#singleFont

当我们在神奇操作的最后一行调用Font.getFont(),我们仍然获得来源三世Pro字体回来了,但潜在的字体的 font2D 现场比原来的字体不同,这种单一的字体现在声称,它知道如何渲染整个字符串。为什么?看来,Java是给我们带回某种知道如何使各种字形,尽管它只能理解的指标对于在底层的源字体中提供的字形frankenfont的。

显示的JFreeChart例如渲染一个更完整的例子在这里,根据关的JFreeChart的例子之一:的https: //gist.github.com/sdudley/b710fd384e495e7f1439 从这个例子中的输出如下所示。

的来源三世Pro字体(不正确地制定了)例如:

用国际音标日文字体(正常布局)例如:


解决方案

我终于想通了。有若干的根本原因,这进一步通过跨平台变异的加入剂量受阻

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

发生的布局问题,因为JFreeChart的是使用的不同字体的对象的一个比AWT实际使用来渲染字体无意中计算布局的指标。 (仅供参考,JFreeChart的计算发生在 org.jfree.text#getTextBounds

究其原因,不同的字体对象是隐式的魔术手法,在问题中提到,这是内部的 java.awt.font.TextLayout中的#singleFont

魔法操纵那些三行可以浓缩到眼前这个:

 字体= Font.getFont(font.getAttributes())

在英国,这一要求的字体管理器给我们根据所提供的字体的属性(姓名,家庭,指向大小等)的新Font对象。在某些情况下,在字体它给回你会从您最初开始使用字体不同。

要修正指标(从而修复布局),的解决方法是设置字体在你自己的字体对象上运行上面的单行在JFreeChart的对象

这样做了以后,布局工作对我很好,就像日本的字符。它应该修复布局对你太,虽然它可能无法正确​​显示日语字符的的。阅读下面有关本机的字体明白为什么。

Mac OS X的字体管理器prefers到返回本机的字体,即使你给它一个物理TTF文件

文字的​​布局是固定的上述变化......但为什么会发生这种情况?在什么情况下的FontManager实际上给我们带回一个不同类型的字体对象比我们提供的吗?

有很多原因,但至少在Mac OS X中,涉及到这个问题的原因是,字体管理器似乎的 preFER以尽可能返回原始字体

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

在我而言,这个扔了红鲱鱼进入调查:我已经有源三世的字体安装在我的Mac上,这意味着我正在从谁没有人有不同的结果。

的Mac OS X原生字体一如既往地支持亚洲字符

而问题的关键是,Mac OS X的的CFont 对象的一如既往地支持亚洲字符集的。我不清楚,让这个确切机制,但我怀疑这是某种形式的OS X本身不是Java的备用字体功能。在这两种情况下,的CFont 总是声称(并且是真正能够)使得亚洲字符用正确的字形。

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


  • 我们创建物理字体从物理TTF文件,它本身并不支持日本。

  • 如上面也安装在我的Mac OS X字体册同一物理字体

  • 计算图表的布局时,JFreeChart的要求物理字体对象为日本文字的指标。物理字体不能这样做,因为正确不支持亚洲字符集。

  • 实际绘制图表时,魔术操纵的TextLayout#singleFont 导致它获得的CFont 对象,使用相同名称的本地字体绘制字形,与物理 TrueTypeFont 。因此,字形是正确的,但他们没有适当的位置。

你会得到不同的结果,这取决于你注册的字体,以及是否有字体安装在你的操作系统

如果你调用 Font.getFont()从创建的TTF字库的属性,你会得到三个不同的结果,具体取决于该字体是否被注册,你是否有本地安装相同的字体:


  • 如果您已使用相同的名称作为您的TTF字体(无论是否注册的字体或没有)安装在本地平台的字体,你会得到一个亚裔支持的CFont 为你想要的字体。

  • 如果您注册了TTF 字体在GraphicsEnvironment中,但你没有同名的本地字体,呼吁Font.getFont()将返回一个物理 TrueTypeFont 对象返回。这给你你想要的字体,但你没有得到亚洲字符。

  • 如果您没有注册TTF 字体,你也没有同名的本地字体,呼吁Font.getFont()返回一个亚裔支持的CFont ,但它不会是你所要求的字体。

在事后看来,这一切都不是完全不足为奇。导致:

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

在生产应用程序,我创建一个字体,但我忘了最初与GraphicsEnvironment中注册。如果你还没有注册的字体当您执行操作魔法上面, Font.getFont()不知道该如何找回它,你会得到一个备份,而不是字体。哎呀。

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


  • MAC:龙力大

  • 的Linux版本(CentOS):龙力三世

  • 视窗:宋体

这个错误实际上是为我们的亚洲用户一件好事,因为这意味着与逻辑字体呈现预期的字符集......虽然西方用户没有得到的字符集我们想要的。

既然已经呈现在错误的字体,我们需要无论如何解决日本的布局,我决定,我会更好试图在将来版本的单一通用的字体规范(因而越来越接近trashgod的建议)

此外,该应用程序有字体渲染质量要求,可能并不总是允许使用某些字体,这样一个合理的决定似乎是尝试配置该应用使用龙力三世,这是包含在一个物理字体甲骨文在Java中的所有副本。但是......

龙力三世不能很好地与亚洲字符的所有平台上播放

要尝试使用龙力三世的决定似乎是合理的......但我很快发现,还有在龙力三世是如何处理平台的差异。在Linux和Windows,如果你问的龙力三世字型的副本,你会得到一个物理 TrueTypeFont 对象。但是,字体不支持亚洲字符。

如果你要求龙力三世同样的问题也包含在Mac OS X真的......但如果​​你问了略有不同的名称为LucidaSans(注意空间不足),那么你会得到一个的CFont 支持龙力三世以及亚洲字符,所以你可以有你的蛋糕和熊掌兼得的对象。

在其他平台上,请求LucidaSans产生的标准对话框字体的副本,因为没有这样的字体和Java正在返回其默认值。在Linux上,你在这里有点幸运,因为实际上对话框默认为Lucida三世西方文本(它也使用了亚洲字符一个像样的回退字体)。

这给了我们一个路径来获得(几乎)在同一个物理的字体在所有平台,并且也支持亚洲字符,通过请求这些名字的字体:


  • 的Mac OS X:LucidaSans(产生龙力三世+亚洲备份字体)

  • Linux的:对话(产生龙力三世+亚洲备份字体)

  • 窗口:对话框(产生的宋体的亚洲+备用字体)

我专心致志地摆弄着Windows上的fonts.properties,我无法找到默认为龙力三世字体顺序,所以它看起来像我们的Windows用户需要坚持宋体得到...但至少它不是在视觉上从龙力三世不同,Windows字体渲染质量是合理的。

在哪里做的一切都是结了?

总之,我们现在$简单,只是利用平台的字体p $ ptty。 (我相信@trashgod是有现在的好笑道!)Mac和Linux服务器得到龙力三世时,Windows获得宋体,渲染质量好了,大家都开心!

When rendering a chart with JFreeChart, I noticed a layout problem when the chart's category labels included Japanese characters. Although the text is rendered with the correct glyphs, the text was positioned in the wrong location, presumably because the font metrics were wrong.

The chart was originally configured to use the Source Sans Pro Regular font for that text, which supports only Latin character sets. The obvious solution is to bundle an actual Japanese .TTF font and ask JFreeChart to use it. This works fine, in that the output text uses the correct glyphs and it is also laid out correctly.

My questions

  • How did java.awt end up rendering the Japanese characters correctly in the first scenario, when using a source font that doesn't actually support anything except Latin characters? If it matters, I am testing on OS X 10.9 with JDK 1.7u45.

  • Is there any way to render the Japanese characters without bundling a separate Japanese font? (This is my end goal!) Although the bundling solution works, I don't want to add 6 Mb of bloat to my application if it can be avoided. Java clearly knows how to render the Japanese glyphs somehow even without the font (at least in my local environment)--it's seemingly just the metrics that are busted. I am wondering if this is related to the "frankenfont" issue below.

  • After the JRE performs an internal transformation, why does the Source Sans Pro font tell the caller (via canDisplayUpTo()) that it can display Japanese characters even though it cannot? (See below.)

Edited to clarify:

  • This is a server app, and the text we are rendering will show up in the client's browser and/or in PDF exports. The charts are always rasterized to PNGs on the server.

  • I have no control over the server OS or environment, and as nice as it would be to use the Java-standard platform fonts, many platforms have poor font choices that are unacceptable in my use case, so I need to bundle my own (at least for the Latin fonts). Using a platform font for the Japanese text is acceptable.

  • The app can potentially be asked to display a mix of Japanese and Latin text, without no a priori knowledge of the text type. I am ambivalent about what fonts get used if a string contains mixed languages, so long as the glyphs are rendered correctly.

Details

I understand that java.awt.Font#TextLayout is smart, and that when trying to lay out text, it first asks the underlying fonts whether they can actually render the supplied characters. If not, it presumably swaps in a different font that knows how to render those characters, but this is not happening here, based on my debugging pretty far into the JRE classes. TextLayout#singleFont always returns a non-null value for the font and it proceeds through the fastInit() part of the constructor.

One very curious note is that the Source Sans Pro font somehow gets coerced into telling the caller that it does know how to render Japanese characters after the JRE performs a transformation on the font.

For example:

// 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));

The output of this is:

Font Source Sans Pro can display up to: 0
Font Source Sans Pro can display up to: -1

Note that the three lines of "magic manipulation" mentioned above are not something of my own doing; we pass in the true source font object to JFreeChart, but it gets munged by the JRE when drawing the glyphs, which is what the three lines of "magic manipulation" code above replicates. The manipulation shown above is the functional equivalent of what happens in the following sequence of calls:

  1. org.jfree.text.TextUtilities#drawRotatedString
  2. sun.java2d.SunGraphics2D#drawString
  3. java.awt.font.TextLayout#(constructor)
  4. java.awt.font.TextLayout#singleFont

When we call Font.getFont() in the last line of the "magic" manipulation, we still get a Source Sans Pro font back, but the underlying font's font2D field is different than the original font, and this single font now claims that it knows how to render the entire string. Why? It appears that Java is giving us back some sort of "frankenfont" that knows how to render all kinds of glyphs, even though it only understands the metrics for the glyphs that are supplied in the underlying source font.

A more complete example showing the JFreeChart rendering example is here, based off one of the JFreeChart examples: https://gist.github.com/sdudley/b710fd384e495e7f1439 The output from this example is shown below.

Example with the Source Sans Pro font (laid out incorrectly):

Example with the IPA Japanese font (laid out correctly):

解决方案

I finally figured it out. There were a number of underlying causes, which was further hindered by an added dose of cross-platform variability.

JFreeChart Renders Text in the Wrong Location Because It Uses a Different Font Object

The layout problem occurred because JFreeChart was inadvertently calculating the metrics for the layout using a different Font object than the one AWT actually uses to render the font. (For reference, JFreeChart's calculation happens in org.jfree.text#getTextBounds.)

The reason for the different Font object is a result of the implicit "magic manipulation" mentioned in the question, which is performed inside of java.awt.font.TextLayout#singleFont.

Those three lines of magic manipulation can be condensed to just this:

font = Font.getFont(font.getAttributes())

In English, this asks the font manager to give us a new Font object based on the "attributes" (name, family, point size, etc) of the supplied font. Under certain circumstances, the Font it gives back to you will be different from the Font you originally started with.

To correct the metrics (and thus fix the layout), the fix is to run the one-liner above on your own Font object before setting the font in JFreeChart objects.

After doing this, the layout worked fine for me, as did the Japanese characters. It should fix the layout for you too, although it may not show the Japanese characters correctly for you. Read below about native fonts to understand why.

The Mac OS X Font Manager Prefers to Return Native Fonts Even If You Feed it a Physical TTF File

The layout of the text was fixed by the above change...but why does this happen? Under what circumstances would the FontManager actually give us back a different type of Font object than the one we provided?

There are many reasons, but at least on Mac OS X, the reason related to the problem is that the font manager seems to prefer to return native fonts whenever possible.

In other words, if you create a new font from a physical TTF font named "Foobar" using Font.createFont, and then call Font.getFont() with attributes derived from your "Foobar" physical font...so long as OS X already has a Foobar font installed, the font manager will give you back a CFont object rather than the TrueTypeFont object you were expecting. This seems to hold true even if you register the font through GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont.

In my case, this threw a red herring into the investigation: I already had the "Source Sans" font installed on my Mac, which meant that I was getting different results from people who did not.

Mac OS X Native Fonts Always Support Asian Characters

The crux of the matter is that Mac OS X CFont objects always support Asian character sets. I am unclear of the exact mechanism that allows this, but I suspect that it's some sort of fallback font feature of OS X itself and not Java. In either case, a CFont always claims to (and is truly able to) render Asian characters with the correct glyphs.

This makes clear the mechanism that allowed the original problem to occur:

  • we created a physical Font from a physical TTF file, which does not itself support Japanese.
  • the same physical font as above was also installed in my Mac OS X Font Book
  • when calculating the layout of the chart, JFreeChart asked the physical Font object for the metrics of the Japanese text. The physical Font could not do this correctly because it does not support Asian character sets.
  • when actually drawing the chart, the magic manipulation in TextLayout#singleFont caused it to obtain a CFont object and draw the glyph using the same-named native font, versus the physical TrueTypeFont. Thus, the glyphs were correct but they were not positioned properly.

You Will Get Different Results Depending on Whether You Registered the Font and Whether You Have The Font Installed in Your OS

If you call Font.getFont() with the attributes from a created TTF font, you will get one of three different results, depending on whether the font is registered and whether you have the same font installed natively:

  • If you do have a native platform font installed with the same name as your TTF font (regardless of whether you registered the font or not), you will get an Asian-supporting CFont for the font you wanted.
  • If you registered the TTF Font in the GraphicsEnvironment but you do not have a native font of the same name, calling Font.getFont() will return a physical TrueTypeFont object back. This gives you the font you want, but you don't get Asian characters.
  • If you did not register the TTF Font and you also do not have a native font of the same name, calling Font.getFont() return an Asian-supporting CFont, but it will not be the font you requested.

In hindsight, none of this is entirely surprising. Leading to:

I Was Inadvertently Using the Wrong Font

In the production app, I was creating a font, but I forgot to initially register it with the GraphicsEnvironment. If you haven't registered a font when you perform the magic manipulation above, Font.getFont() doesn't know how to retrieve it and you get a backup font instead. Oops.

On Windows, Mac and Linux, this backup font generally seems to be Dialog, which is a logical (composite) font that supports Asian characters. At least in Java 7u72, the Dialog font defaults to the following fonts for Western alphabets:

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

This mistake was actually a good thing for our Asian users, because it meant that their character sets rendered as expected with the logical font...although the Western users were not getting the character sets that we wanted.

Since it had been rendering in the wrong fonts and we needed to fix the Japanese layout anyway, I decided that I would be better off trying to standardize on one single common font for future releases (and thus coming closer to trashgod's suggestions).

Additionally, the app has font rendering quality requirements that may not always permit the use of certain fonts, so a reasonable decision seemed to be to try to configure the app to use Lucida Sans, which is the one physical font that is included by Oracle in all copies of Java. But...

Lucida Sans Doesn't Play Well with Asian Characters on All Platforms

The decision to try using Lucida Sans seemed reasonable...but I quickly found out that there are platform differences in how Lucida Sans is handled. On Linux and Windows, if you ask for a copy of the "Lucida Sans" font, you get a physical TrueTypeFont object. But that font doesn't support Asian characters.

The same problem holds true on Mac OS X if you request "Lucida Sans"...but if you ask for the slightly different name "LucidaSans" (note the lack of space), then you get a CFont object that supports Lucida Sans as well as Asian characters, so you can have your cake and eat it too.

On other platforms, requesting "LucidaSans" yields a copy of the standard Dialog font because there is no such font and Java is returning its default. On Linux, you are somewhat lucky here because Dialog actually defaults to Lucida Sans for Western text (and it also uses a decent fallback font for Asian characters).

This gives us a path to get (almost) the same physical font on all platforms, and which also supports Asian characters, by requesting fonts with these names:

  • Mac OS X: "LucidaSans" (yielding Lucida Sans + Asian backup fonts)
  • Linux: "Dialog" (yielding Lucida Sans + Asian backup fonts)
  • Windows: "Dialog" (yielding Arial + Asian backup fonts)

I've pored over the fonts.properties on Windows and I could not find a font sequence that defaulted to Lucida Sans, so it looks like our Windows users will need to get stuck with Arial...but at least it's not that visually different from Lucida Sans, and the Windows font rendering quality is reasonable.

Where Did Everything End Up?

In sum, we're now pretty much just using platform fonts. (I am sure that @trashgod is having a good chuckle right now!) Both Mac and Linux servers get Lucida Sans, Windows gets Arial, the rendering quality is good, and everyone is happy!

这篇关于为什么拉丁字符只字体的Java声称支持亚洲字符,即使它不?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆