2023年4月16日日曜日

UTF-8

昨日の記事で、Shift_JISの5C問題について書いた。
「だから、Shift_JISなんてダメダメだ!」なんていう人もいるだろう。
文字を「バイト」として扱うから問題になる。
文字を「文字」として扱えば、'\'記号か、2バイト文字の2バイト目なのかは区別がつく。
ようはそれだけだ。

以前は、組み込み開発では、メモリサイズの都合から基本的に2バイト文字に対応することはなかった。
しかし、マイコン内蔵メモリも増えてきた。ファイルシステムを扱うようになると、ファイル名に2バイト文字が含まれるため、しばしば2バイト文字の扱いが必要になることもある(2バイト文字を扱わないというソリューションもあると思うけど)。
そのため、十数年前から組み込み環境へマルチバイト文字を扱う関数群を移植することも増えた。

UTF-8は、C5問題のような問題はない。
UTF-8では、複数バイトで構成される文字のバイト値に、ASCII 7bit文字のバイト値が含まれないためだ。
そのため、5Cは常に'\'であり、文字を「文字」として扱わなくてもこの問題は生じない。

じゃ、何も考えなくていいのかというと、そうでもない。
例えば「泣き別れ問題」は、Shift_JIS同様存在する。
例えば、何らかの理由で文字列の一部を区切る必要があるとき、複数バイトで構成される文字の途中で区切るわけにはいかない。
UTF-8の1文字を構成するバイト列が泣き別れになると、Unicodeへデコードできなくなる。
そのため、任意のバイトでは区切れない。結局、文字を「文字」として扱う必要がある。

文字列を区切る理由の1つに、物理的な表示幅の制限がある。
プロポーショナルフォントの場合、文字毎に、場合によると文字の組み合わせや順序により、文字列の幅が変わる。
そのため、常に文字列として幅を調べなければならないため、1バイト文字もマルチバイト文字も関係ない。

厄介なのは端末アプリだ。
日本語対応端末は伝統的に、英数や半角カナは半角(1カラム)、漢字は全角(2カラム)で表示される。
Unicodeでは、半角、全角以外に、N(中立)、'A'(曖昧な幅)があり、これらの文字のカラム数はコンテキスト依存して変化する。
(Wikipediaの「東アジアの文字幅」参照)
すなわち「よくわからない」幅なのだ。
やや乱暴なソリューションとして、『「よくわからない」を使用しない』というのも考えられる。
しかし、Shift_JISでも存在していたギリシャ文字やキリル文字、一部の記号も、'A'に分類されている。
これらの文字を使わないとするのは、さすがに乱暴すぎるだろう。
コード体系もJIS漢字とは異なるため、これらの文字幅を自力で解決しなければならない。

20年以上前から、自家製FATファイルシステムドライバを実装していた。
当初はFAT12/16のみ対応だったけど、途中からFAT32に対応し、Long File Nameにも対応した。
Long File Nameにしっかり対応するとなると、Unicode - CP932(Shift_JIS)との相互変換が必要になる。
そのころ、UTF-8-cjk.txtという文書が入手可能だったのだので、その情報を元に、変換テーブルを作った。

なんと、そのファイルの末尾には、Unicode文字の文字幅の情報も付いていた。
この文字幅情報を使って、日本語UTF-8テキストファイルのTAB-Space変換を実装したりもした。
さらに、端末を用いた組み込みでのコマンドライン処理で、UTF-8文字を扱う際の、カラム数の算出にも使った。
今では自家製組み込みコマンドライン処理は、Shift_JISとUTF-8の両方に対応している(コンパイル時に決定)。

ところが、そのファイルはいつの間にかダウンロードできなくなっていた。
今は、EastAsianWidth.txtで文字幅の情報が得られる。

ここまでやれば、UTF-8での文字列の幅に対応できるようになる。
私は、Shift_JISであれ、UTF-8であれ、どっちでも同じように実用上問題なく扱えている。
(Shift_JISというか、CP932というか、には例外的なものが多く、Unicodeは今でも成長を続けているので、完璧ではないし、完璧と言えるものは無いだろう)

「UTF-8がいい」「Shift_JISなんてダメダメだ!」なんて言っている人、ちゃんと理解できているかな?

2023/04/23 追記:文字幅0の文字について 制御文字やウムラウト(日本語では濁点や半濁点)等、一部の文字は幅がない。
これらの文字幅も正しく扱うなら、UnicodeData.txt から情報を抽出する必要がある。
検索すればすぐに出てくる。とはいえ、ダウンロードして手元に置いておくほうがいいだろう。
メンテ等で時々ダウンロードできなくなる。

抽出方法については、UTF-8-cjk.txtに書いてあった。
抜粋すると、
% Character width according to Unicode 5.0.0.
% - Default width is 1.
% - Double-width characters have width 2; generated from
%        "grep '^[^;]*;[WF]' EastAsianWidth.txt"
%   and  "grep '^[^;]*;[^WF]' EastAsianWidth.txt"
% - Non-spacing characters have width 0; generated from PropList.txt or
%   "grep '^[^;]*;[^;]*;[^;]*;[^;]*;NSM;' UnicodeData.txt"
% - Format control characters have width 0; generated from
%   "grep '^[^;]*;[^;]*;Cf;' UnicodeData.txt"
% - Zero width characters have width 0; generated from
%   "grep '^[^;]*;ZERO WIDTH ' UnicodeData.txt"
実は、文字幅0の一部が文字幅2と被っている。
これもまた表示環境に依存する。

0 件のコメント:

コメントを投稿