2012-05-25 2 views
5

Когда я использую Perl или C, чтобы printf некоторые данные, которые я пробовал их формат, чтобы контролировать ширину каждого столбца, какUTF-8 Ширина дисплея Выпуск китайских иероглифов

printf("%-30s", str); 

Но когда Обл содержит китайский иероглиф, то столбец не выравнивается так, как ожидалось. см. Изображение вложений.

Кодировка кодировки ubuntu - zh_CN.utf8, насколько я знаю, кодировка utf-8 имеет длину 1 ~ 4 байта. Китайский символ имеет 3 байта. В моем тесте я обнаружил, что формат формата printf имеет китайский символ как 3, но на самом деле он отображает 2 ширины ascii.

Таким образом, ширина реальный дисплей не является постоянной величиной, как ожидается, но переменная связана с числом китайского иероглифа, т.е.

Sw(x) = 1 * (w - 3x) + 2 * x = w - x 

ш предел ширины ожидается, х является счетчиком китайских иероглифов, Sw (x) - реальная ширина дисплея.

Таким образом, чем больше символов китайского символа содержит, тем короче оно отображается.

Как я могу получить то, что хочу? Подсчитайте китайские символы перед printf?

Насколько я знаю, все китайские или даже все широкие символы, я думаю, отображаются как 2 ширины, то почему printf считает это 3? Кодировка UTF-8 не имеет никакого отношения к длине отображения.

+0

Другими словами, вы ищете версию «printf» с многобайтовым именем для Perl и/или C? – deceze

+0

Я никогда не делал декодирования utf8 в C, но вот код Go, который подсчитывает руны в строке utf-8: http://golang.org/src/pkg/unicode/utf8/utf8.go?s=4824:4876 # L202 –

+1

@dystroy Это не просто вопрос подсчета кодовых точек (т. Е. Рун). Скорее, он учитывает, что разные кодовые точки представляют 0, 1 или 2 столбца печати на UAX # 11, и это довольно тонко, особенно с символами 'East_Asian_Width = Ambiguous'. Я не знаю ни одной библиотеки Go, которая имеет дело с этим, как это делает библиотека Perl, описанная в моем ответе, но если есть что-то для Go, я хотел бы узнать об этом! Благодарю. – tchrist

ответ

6

Да, это проблема со всеми версиями printf, о которых я знаю. Я кратко обсужу этот вопрос в this answer, а также в this one.

Для C я не знаю библиотеки, которая сделает это за вас, но если у кого-то это будет, это будет ICU.

Для Perl вам необходимо использовать модуль CPAN CPAN для расчета количества столбцов печати, которые займет строка Юникода. Это учитывает Unicode Standard Annex #11: East Asian Width.

Например, некоторые пункты кода занимают 1 столбец, а другие занимают 2 столбца. Есть даже те, которые вообще не занимают никаких столбцов, например, комбинируя символы и невидимые управляющие символы. Класс имеет метод columns, который возвращает количество столбцов, которые занимает строка.

У меня есть пример использования этого для выравнивания текста Unicode по вертикали here. Он будет сортировать кучу строк Unicode, включая некоторые из них, сочетающие символы и «широкие» азиатские идеограммы (символы CJK) и позволяющие выравнивать объекты по вертикали.

sample terminal output

Код для маленькой umenu демо-программы, которая печатает что хорошо выровненный выход, включенная ниже.

Вам также может быть интересен более амбициозный модуль Unicode::LineBreak, из которых вышеупомянутый класс Unicode::GCString представляет собой меньший компонент. Этот модуль намного круче и учитывает Unicode Standard Annex #14: Unicode Line Breaking Algorithm.

Код ошибки для маленького 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; 
} 

Как вы видите, ключ, чтобы сделать его работу в этой конкретной программе эта строка кода, которая просто вызывает другие функции, определенные выше, и использует модуль я обсуждала:

print pad(entitle($item), $width, "."); 

Это выметит элемент с заданной шириной, используя точки в качестве символа заполнения.

Да, это намного менее удобно, чем printf, но, по крайней мере, это возможно.

Смежные вопросы