21

在 Unicode 的不同编码中,例如UTF-16leUTF-8,一个字符可能占用 2 或 3 个字节。许多 Unicode 应用程序不关心 Unicode 字符的显示宽度,就像它们都是拉丁字母一样。例如,在80列的文本中,一行应该包含40 个汉字或80 个拉丁字母,但大多数应用程序(如 Eclipse、Notepad++ 和所有知名的文本编辑器,如果有什么好的例外我敢说)只是计算每个汉字为 1 宽度为拉丁字母。这肯定会使结果格式丑陋且不对齐。

例如,tab-width 为 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 字符集(或GBK字符集)中不存在某些 Unicode 字符。而且,一般来说,从编码大小(以字节为单位)计算显示宽度并不是一个好主意。

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

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

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

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

4

5 回答 5

25

听起来您正在寻找类似wcwidthand的东西wcswidth,在 IEEE Std 1003.1-2001 中定义,但从 ISO C 中删除:

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

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

在固定宽度的输出设备中,拉丁字符都占据一个等宽的“单元”位置,而表意的 CJK 字符占据两个这样的单元。终端行应用程序和使用 UTF-8 编码的(电传式)字符终端之间的互操作性要求就哪个字符应该使光标前进多少单元格位置达成一致。目前还没有既定的正式标准,关于哪个 Unicode 字符应在字符终端上占据多少个单元格位置。这些例程是基于应用于 Unicode 联盟提供的数据的简单规则定义此类行为的第一次尝试。[...]

它执行以下规则:

  • 空字符 (U+0000) 的列宽为 0。
  • 其他 C0/C1 控制字符和 DEL 将导致返回值 -1。
  • 非间距和封闭组合字符(Unicode 数据库中的通用类别代码 Mn 或 Me)的列宽为 0。
  • SOFT HYPHEN (U+00AD) 的列宽为 1。
  • 其他格式字符(Unicode 数据库中的通用类别代码 Cf)和零宽度空格 (U+200B) 的列宽为 0。
  • Hangul Jamo 中元音和结尾辅音 (U+1160-U+11FF) 的列宽为 0。
  • Unicode 技术报告 #11 中定义的东亚宽 (W) 或东亚全宽 (F) 类别中的间距字符的列宽为 2。
  • 所有剩余字符(包括所有可打印的 ISO 8859-1 和 WGL4 字符、Unicode 控制字符等)的列宽为 1。
于 2012-02-04T23:54:51.543 回答
5

您混淆了代码点、字形和编码。

编码是将代码点转换为八位字节流以进行存储、传输或处理的方式。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 以获得 Kwakwala 中使用的带有急性和下划线的小写字母) . 在某些情况下,有多个代码点组合来创建相同的字素(例如,带有 ACUTE 和 COMBINING UNDERSCORE 的拉丁小写字母 A),这是“规范化”,

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

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

有关更多信息,请参阅Unicode维基百科和Unicode 主页。还有一些优秀的书籍,也许最着名的是O'Reilly 的 Yannis Haralambous 所著的《字体与编码》。

于 2010-09-03T10:08:51.530 回答
5

反映此概念的 Unicode 属性是East_Asian_Width。在一般 Unicode 渲染的上下文中,作为视觉宽度并不是很可靠,因为非亚洲字符、组合字符等即使在等宽字体中也无法对齐。(您的示例当然不会为我排好队。)

Java 没有内置的读取字符属性的能力(尽管Android 的扩展有)。如果你真的需要它,你可以从ICU4J得到它。

于 2010-09-04T06:57:58.343 回答
3

我相信要正确执行此操作,您需要考虑已发布的 Unicode 标准的组件,即Unicode 标准附件 #14,即Unicode 换行算法

如果您使用 Perl 进行编程,那么您想知道的内容会非常简单,因为 Perl 的Unicode::LineBreak模块实现了 UAX#14,其中包含一个类,该类具有一个简单的columns方法,可以告诉您其字符串参数的正确答案。这些东西在亚洲语言上特别有效,在亚洲语言中绝对没有其他办法。该模块包含超过 6,000 个单元测试,并得到积极维护,其作者本人是亚洲人,因此对他来说,准确地正确处理这些棘手的部分非常重要。

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

于 2012-02-09T11:41:45.300 回答
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 的任何地方工作(它在无头环境中运行良好)。

于 2012-07-25T18:26:30.923 回答