如何知道Unicode字符的首选显示宽度(以列为单位)?

时间:2010-09-03 09:54:01

标签: unicode text-formatting character-properties mbcs

在Unicode的不同编码中,例如 UTF-16le UTF-8 ,字符可能占用2或3个字节。许多Unicode应用程序不像处理所有拉丁字母那样处理Unicode字符的显示宽度。例如,在 80 列文本中,其中一行应包含 40 中文字符或 80 拉丁字母,但大多数应用程序(如Eclipse) ,Notepad ++,以及所有着名的文本编辑器,我敢说,如果有任何好的例外)只需将每个汉字计为1宽度为拉丁字母。这肯定会使结果格式变得丑陋且不对齐。

例如,制表符宽度为8将获得以下难看的结果(将所有Unicode计为1个显示宽度):

apple   10
banana  7
苹果      6
猕猴桃     31
pear    16

但是,预期的格式是(将每个汉字计为2宽度):

apple   10
banana  7
苹果    6
猕猴桃  31
pear    16

对字符显示宽度的不正确计算使得这些编辑器在进行制表符对齐,换行和段重组时完全没用。

虽然,不同字体的字符宽度可能不同,但在固定大小的终端字体的所有情况下,汉字总是双倍宽度。也就是说,尽管有字体,但每个汉字最好以2宽度显示。

解决方案之一是,我可以通过将编码转换为 GB2312 来获得正确的宽度,在 GB2312 编码中,每个中文字符占用2个字节。但是,GB2312 charset(或 GBK charset)中不存在某些Unicode字符。而且,一般来说,从编码大小(以字节为单位)计算显示宽度并不是一个好主意。

简单地计算(\u0080 .. \uFFFF)范围内Unicode中的所有字符,因为2宽度也不正确,因为在该范围内还散布着许多1宽度字符。

计算阿拉伯字母和韩文字母的显示宽度时也很困难,因为它们通过任意数量的Unicode代码点构造一个单词/字符。

因此,Unicode代码点的显示宽度可能不是整数,我认为没问题,它们可以在实践中基于整数,至少比没有好。

那么,在Unicode标准中是否有与char的首选显示宽度相关的属性? 或者任何Java库函数来计算显示宽度?

5 个答案:

答案 0 :(得分:20)

听起来像是在寻找IEEE标准1003.1-2001中定义的wcwidthwcswidth,但已从ISO C中删除:

  

wcwidth()函数应确定列位置的数量   宽字符 wc 所需。 wcwidth()函数应该   要么返回0(如果 wc 是一个空的宽字符代码),要么返回   宽字符代码占用的列位置数    wc ,或返回-1(如果 wc 与可打印不对应   宽字符代码)。

Markus Kuhn基于Unicode 5.0编写了一个开源版本wcwidth.c。它包括对问题的描述,以及对该领域缺乏标准的承认:

  

在固定宽度输出设备中,拉丁字符全部占用一个   “细胞”位置宽度相等,而表意文字为CJK字符   占据两个这样的细胞。终端线之间的互操作性   应用程序和使用UTF-8的(电传打字机)字符终端   编码需要就哪个角色应该推进而达成协议   光标由多少个单元格位置组成。没有既定的正式标准   目前存在的Unicode字符应占用多少个单元格   在角色终端上的位置。这些例程是第一次尝试   基于应用于数据的简单规则来定义此类行为   由Unicode Consortium提供。 [...]

它实现了以下规则:

  • 空字符(U + 0000)的列宽为0.
  • 其他C0 / C1控制字符和DEL将导致返回值为-1。
  • 非间距和包含组合字符(Unicode数据库中的常规类别代码Mn或Me)的列宽为0。
  • SOFT HYPHEN(U + 00AD)的列宽为1.
  • 其他格式字符(Unicode数据库中的常规类别代码Cf)和ZERO WIDTH SPACE(U + 200B)的列宽为0。
  • Hangul Jamo内侧元音和最后辅音(U + 1160-U + 11FF)的列宽为0。
  • Unicode技术报告#11中定义的东亚宽(W)或东亚全宽(F)类别中的间距字符的列宽为2.
  • 所有剩余字符(包括所有可打印的ISO 8859-1和WGL4字符,Unicode控制字符等)的列宽为1.

答案 1 :(得分:4)

您会混淆代码点,字形和编码。

编码是将代码点转换为八位字节流以进行存储,传输或处理的方式。 UTF-8和UTF-16都是可变宽度编码,不同的代码点需要不同数量的八位字节(对于UTF-8,从1到IIRC,6和UTF-16,无论是2还是4)。

字素是“我们所看到的字符”,这些是显示的内容。一个字母的一个代码点(例如LATIN LOWER CASE A),但在其他情况下可能需要多个代码点(例如,LATIN LOWER CASE A,COMBINING ACUTE和COMBINING UNDERSCORE以获得具有急性和下划线的小写,如{{{ 3}})。在某些情况下,有一个以上的代码点组合来创建相同的字形(例如,拉丁文案例A与急性和组合理解),这是“规范化”,

即。单个字素的编码长度取决于编码和规范化。

字素的显示宽度取决于字体,样式和大小,与编码长度无关。

有关更多信息,请参阅KwakwalaUnicode上的维基百科。还有一些优秀的书籍,也许最引人注目的是“Unicode's home”,作者是Yannis Haralambous,O'Reilly。

答案 2 :(得分:3)

反映此概念的Unicode属性为East_Asian_Width。它在一般Unicode渲染的上下文中作为视觉宽度并不真正可靠,因为非亚洲字符,组合字符等将无法排列即使是等宽字体。 (你的例子当然不能为我排队。)

Java没有为字符读取此属性的内置功能(尽管Android's extension确实如此)。如果您确实需要,可以从ICU4J获取。

答案 3 :(得分:2)

我认为要正确执行此操作,您需要考虑已发布的U​​nicode标准的组件Unicode Standard Annex #14, the Unicode Line Breaking Algorithm.

如果您使用Perl进行编程,那么您想要了解的内容将非常简单,因为实现UAX#14的Perl的Unicode::LineBreak模块包含一个带有简单columns方法的类,可以告诉您正确的答案为其字符串参数。这些东西在亚洲语言中特别有效,绝对没有别的东西可以做。这个模块包括6,000多个单元测试,并且是积极维护的,而且它的作者本身就是亚洲人,所以让他们把这些棘手的东西完全正确对他来说很重要。

模块的大部分内容都是用C编写的库。我没有看过如何从Perl的其他语言调用它的组件C库,但是你可能会研究这是否可能。

答案 4 :(得分:1)

关于“或任何用于计算显示宽度的Java库函数?”:如果有,我从未找到它。

计算字符/字符串宽度的最简单方法是将其写入GNU unicode字体(http://unifoundry.com/unifont.html)&测量字符宽度。不干净,但到目前为止它适用于我能想到的每种编码。

FWIW这就是我的所作所为:

java.awt.font.Font MONOSPACEFONT = Font.createFont(Font.TRUETYPE_FONT, 
    new File("unifont-5.1.20080907.ttf"));

java.awt.font.FontRenderContext FRC = new FontRenderContext(null, true, true);

int charWidth =  (int) (2.0*((java.awt.geom.Rectangle2D.Float) 
    MONOSPACEFONT.getStringBounds(stringToMeasure, FRC)).width);

...这应该可以在您部署JVM的任何地方运行(它在无头环境中运行良好)。