在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库函数来计算显示宽度?
答案 0 :(得分:20)
听起来像是在寻找IEEE标准1003.1-2001中定义的wcwidth
和wcswidth
,但已从ISO C中删除:
wcwidth()
函数应确定列位置的数量 宽字符 wc 所需。wcwidth()
函数应该 要么返回0(如果 wc 是一个空的宽字符代码),要么返回 宽字符代码占用的列位置数 wc ,或返回-1(如果 wc 与可打印不对应 宽字符代码)。
Markus Kuhn基于Unicode 5.0编写了一个开源版本wcwidth.c。它包括对问题的描述,以及对该领域缺乏标准的承认:
在固定宽度输出设备中,拉丁字符全部占用一个 “细胞”位置宽度相等,而表意文字为CJK字符 占据两个这样的细胞。终端线之间的互操作性 应用程序和使用UTF-8的(电传打字机)字符终端 编码需要就哪个角色应该推进而达成协议 光标由多少个单元格位置组成。没有既定的正式标准 目前存在的Unicode字符应占用多少个单元格 在角色终端上的位置。这些例程是第一次尝试 基于应用于数据的简单规则来定义此类行为 由Unicode Consortium提供。 [...]
它实现了以下规则:
答案 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与急性和组合理解),这是“规范化”,
即。单个字素的编码长度取决于编码和规范化。
字素的显示宽度取决于字体,样式和大小,与编码长度无关。
有关更多信息,请参阅Kwakwala和Unicode上的维基百科。还有一些优秀的书籍,也许最引人注目的是“Unicode's home”,作者是Yannis Haralambous,O'Reilly。
答案 2 :(得分:3)
反映此概念的Unicode属性为East_Asian_Width。它在一般Unicode渲染的上下文中作为视觉宽度并不真正可靠,因为非亚洲字符,组合字符等将无法排列即使是等宽字体。 (你的例子当然不能为我排队。)
Java没有为字符读取此属性的内置功能(尽管Android's extension确实如此)。如果您确实需要,可以从ICU4J获取。
答案 3 :(得分:2)
我认为要正确执行此操作,您需要考虑已发布的Unicode标准的组件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的任何地方运行(它在无头环境中运行良好)。