gpa*_*nda 5 c unicode perl utf-8 vertical-alignment
当我使用Perl或C来处理printf某些数据时,我尝试使用它们的格式来控制每列的宽度,比如
printf("%-30s", str);
Run Code Online (Sandbox Code Playgroud)
但是当str包含中文字符时,该列不会按预期对齐.看附件图片.
我的ubuntu的charset编码是zh_CN.utf8,据我所知,utf-8编码有1~4个字节长度.汉字有3个字节.在我的测试中,我发现printf的格式控件将中文字符计为3,但它实际上显示为2 ascii宽度.
因此,实际显示宽度不是预期的常数,而是与汉字数量相关的变量,即
Sw(x) = 1 * (w - 3x) + 2 * x = w - x
Run Code Online (Sandbox Code Playgroud)
w是预期的宽度限制,x是中文字符的数量,Sw(x)是实际显示宽度.
因此,中文字符str包含的越多,它显示的越短.
我怎样才能得到我想要的东西?在printf之前计算汉字?
据我所知,我猜所有的中文甚至所有宽字都显示为2宽,那么为什么printf算为3呢?UTF-8的编码与显示长度无关.
是的,这是printf我所知道的所有版本的问题.我简要地讨论此事这个答案也是在这一个.
对于C,我不知道哪个库会为你做这个,但如果有人有它,它将是ICU.
对于Perl,您必须使用CPAN 的Unicode :: GCString模块来计算Unicode字符串将占用的打印列数.这考虑了Unicode标准附件#11:东亚宽度.
例如,某些代码点占用1列而其他代码占用2列.甚至有一些根本不占用任何列,例如组合字符和不可见的控制字符.该类有一个columns方法,返回字符串占用的列数.
我已经使用这个为垂直对齐Unicode文本的例子在这里.它将对一堆Unicode字符串进行排序,包括一些组合字符和"宽"亚洲表意文字(CJK字符),并允许您垂直对齐.

umenu下面列出了用于打印精确对齐输出的小型演示程序的代码.
您可能也对更加雄心勃勃的Unicode :: LineBreak模块感兴趣,其中上述Unicode::GCString类只是一个较小的组件.该模块更酷,并考虑到Unicode标准附件#14:Unicode断行算法.
这是umenu在Perl v5.14上测试的小型演示代码:
#!/usr/bin/env perl
# umenu - demo sorting and printing of Unicode food
#
# (obligatory and increasingly long preamble)
#
use utf8;
use v5.14; # for locale sorting
use strict;
use warnings;
use warnings qw(FATAL utf8); # fatalize encoding faults
use open qw(:std :utf8); # undeclared streams in UTF-8
use charnames qw(:full :short); # unneeded in v5.16
# std modules
use Unicode::Normalize; # std perl distro as of v5.8
use List::Util qw(max); # std perl distro as of v5.10
use Unicode::Collate::Locale; # std perl distro as of v5.14
# cpan modules
use Unicode::GCString; # from CPAN
# forward defs
sub pad($$$);
sub colwidth(_);
sub entitle(_);
my %price = (
"?????" => 6.50, # gyros, Greek
"pears" => 2.00, # like um, pears
"linguiça" => 7.00, # spicy sausage, Portuguese
"xoriço" => 3.00, # chorizo sausage, Catalan
"hamburger" => 6.00, # burgermeister meisterburger
"éclair" => 1.60, # dessert, French
"smørbrød" => 5.75, # sandwiches, Norwegian
"spätzle" => 5.50, # Bayerisch noodles, little sparrows
"??" => 7.50, # bao1 zi5, steamed pork buns, Mandarin
"jamón serrano" => 4.45, # country ham, Spanish
"pêches" => 2.25, # peaches, French
"???????" => 1.85, # cream-filled pastry like éclair, Japanese
"???" => 4.00, # makgeolli, Korean rice wine
"??" => 9.99, # sushi, Japanese
"???" => 2.65, # omochi, rice cakes, Japanese
"crème brûlée" => 2.00, # tasty broiled cream, French
"fideuà" => 4.20, # more noodles, Valencian (Catalan=fideuada)
"pâté" => 4.15, # gooseliver paste, French
"?????" => 8.00, # okonomiyaki, Japanese
);
my $width = 5 + max map { colwidth } keys %price;
# So the Asian stuff comes out in an order that someone
# who reads those scripts won't freak out over; the
# CJK stuff will be in JIS X 0208 order that way.
my $coll = new Unicode::Collate::Locale locale => "ja";
for my $item ($coll->sort(keys %price)) {
print pad(entitle($item), $width, ".");
printf " €%.2f\n", $price{$item};
}
sub pad($$$) {
my($str, $width, $padchar) = @_;
return $str . ($padchar x ($width - colwidth($str)));
}
sub colwidth(_) {
my($str) = @_;
return Unicode::GCString->new($str)->columns;
}
sub entitle(_) {
my($str) = @_;
$str =~ s{ (?=\pL)(\S) (\S*) }
{ ucfirst($1) . lc($2) }xge;
return $str;
}
Run Code Online (Sandbox Code Playgroud)
如您所见,使其在该特定程序中工作的关键是这行代码,它只调用上面定义的其他函数,并使用我正在讨论的模块:
print pad(entitle($item), $width, ".");
Run Code Online (Sandbox Code Playgroud)
这将使用点作为填充字符将项目填充到给定宽度.
是的,它不太方便printf,但至少有可能.
| 归档时间: |
|
| 查看次数: |
2326 次 |
| 最近记录: |