おそらくはそれさえも平凡な日々

Perlでマルチバイト文字の文字幅をAmbiguousWidthも考慮して取りたいときのTips

08/06 17:45 追記

tokuhiromがText::VisualWidth::PP 0.02を出してくれました。

これには、$Text::VisualWidth::PP::EastAsianというフラグ変数が加わっていて、これにtrue valueをセットすることでAmbiguousWidthの扱いを変更できます。つまりこれでいける。

use Text::VisualWidth::PP;
$Text::VisualWidth::PP::EastAsian = 1;

my $chr = '…';
print Text::VisualWidth::PP::width($chr); # 2

これで変なバッドノウハウを避けられて万事解決な感じです。tokuhirom++

tl;dr

曖昧幅も全角幅とみなして幅を取りたい場合は以下のようにすれば良い。tokuhiromに指摘を受けて修正しました。

BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; }
use Unicode::EastAsianWidth;
BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; }

use Text::VisualWidth::PP;
my $chr = '…';
print Text::VisualWidth::PP::width($chr); # 2

BEGINを2回指定するという力技を使うことにより、Perlバージョンに依存しないスマート()な実装が実現。

Perl5.14以下だと後ろのBEGINは削除可能で、Perl5.16以降だと前のBEGINが削除可能。

ちなむと、Text::VisualWidthだと曖昧幅の対応はできない。

本題

Perlでマルチバイトの文字幅を取りたい場合はText::VisualWidth::PPを使うのが定番なわけです。

Text::VisualWidthの方がXSで高速だったりするわけですが、長いことメンテされておりません。

かたや、Text::VisualWidth::PPはUnicode::EastAsianWidthに依存していて、Unicode::EastAsianWidthはUnicodeがアップデートされるたびにきちんと更新されているので、今のところは新しい文字にも対応できて安心できるというのもあります。

さて、Text::VisualWidth::PPを使っていて困るのは、AmbiguousWidth領域の文字が1幅に扱われてしまうことです。

AmbiguousWidth領域ってのは、その名の通り全角幅か半角幅かよくわからない文字の領域なのですが、この領域は全角幅として扱ったほうが都合が良いことが多いのですが、Text::VisualWidth::PP::width()ではこれらを標準では半角幅として扱ってしまいます。

AmbiguousWidth領域に含まれる文字としては例えば、'…', '○', '△', '①'、などがあります。これらの文字は全角幅として扱って欲しいところでしょう。

解決策としては、Unicode::EastAsianWidthに曖昧幅を全角幅として扱うように挙動を変える$Unicode::EastAsianWidth::EastAsianというフラグ変数があるので、これに1をセットしてあげれば良い。

ただ、これのセットの仕方がちょっとトリッキーで、ユーザー定義Unicodeプロパティの扱いに関する問題で細かいことはわからないのですが、 perl5.14以前の場合は use Unicode::EastAsianWidth; の前でBEGINで括って1をセットする必要があり、Perl5.16以降の場合は、useした後にBEGINで括って代入する必要があります。以下の様な具合です。

use Unicode::EastAsianWidth;
BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; } # この場合でもBEGINが必要だった

Perl5.14以下の場合。

BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; }
use Unicode::EastAsianWidth;

ただし、Perl5.16以降の挙動に関してはundocumentedで、このフラグはあんまちゃんと動かないから非推奨です、とUnicode::EastAsianWidthのPodに書かれていることに留意しましょう。

結論として、曖昧幅を全角幅とみなしてちゃんと文字幅を取りたい場合は以下のようにすれば良い。BEGINが2つあるのが気になるようだったら、適宜削って下さい。

BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; }
use Unicode::EastAsianWidth;
BEGIN { $Unicode::EastAsianWidth::EastAsian = 1; }

use Text::VisualWidth::PP;
my $chr = '…';
print Text::VisualWidth::PP::width($chr); # 2

なんか文字幅とかちゃんと取れないと、Shift_JISおじさんという名のcp932老害が「Shift_JISは文字幅とバイトが一致するから最高だよねー。UTF-8、m9(^Д^)プギャー」とか言い出すので危険が危ない。Shift_JISはEUC-JPと違って半角カナが文字幅通り1バイトだったりするのも素晴らしいですね(棒)

余談ですが、OSXもMountain LionからTerminal.appの設定に、"Unicode 東アジア A (曖昧) の文字幅を W (広) にする" (Unicode East Asian Ambiguous characters are wide)というのが加わっており、曖昧幅を適切に扱えるようになっております。てことで最近はiTerm2を使わずにTerminal.appを使う生活をしております。

created at
last modified at

2013-08-06T17:49:49+0900

comments powered by Disqus