最近、矢野啓介著「文字コード技術入門」(技術評論社・2010)を読みました。 その中でも興味深かった Unicode の結合文字について簡単に紹介します。
Unicode における、符号位置の結合による文字表現
Unicode では、複数の文字の羅列を、新たな「1文字」として表現する方法が存在します。。
例えば、か゚
という文字列がそうです。
これは、「か」と半濁音を足し合わせた文字で、符号位置で表現すると下記の通りです。
か U+304B
+ ◌ ゚ U+309A
= か゚ U+304B U+309A
ターミナル上で、実際にバイト列を足し合わせて か゚
を表現してみましょう。
符号化方式として UTF-8
を例にすると、バイト列は次のようになります。
か (e3 81 8b
) + ◌ ゚ (e3 82 9a
) = か゚ (e3 81 8b e3 82 9a
)
1# 末尾の 0x0a は LF(改行)2▶ echo "e3 81 8b e3 82 9a 0a" | xxd -p -r3か゚
xxd
… テキスト → バイナリ変換するコマンド
-r
… リバース(バイナリ → テキスト)
-p
… 16 進数で入出力
文字の結合で表現可能な絵文字
同じように、絵文字においても、複数の異なる符号位置の足し合わせで新しい符号位置を表現しているものがあります。その一覧は下記のリストから見ることができます。
Unicode Recommended Emoji ZWJ Sequences
例えば以下のようなものです。
😵 + 💫 = 😵💫
絵文字の場合は、単に符号を並べるだけでなく ZWJ (Zero Width Joiner U+200D
) という文字を「演算子」として利用します。
これは、Unicode におけるアラビア文字などで、複数の文字列を結合する際に使用する制御文字です。Wikipedia - ゼロ幅接合子
”😵” (U+1F635
) + “zwj” + (U+200D
) + ”💫” (U+1F4AB
)
= 😵💫 U+1F635 U+200D U+1F4AB
ここで、UTF-8 のバイト列を使って「演算」してみましょう(?)
1▶ echo "😵" | xxd200000000: f09f 98b5 0a .....34▶ echo "💫" | xxd500000000: f09f 92ab 0a .....
↑ の末尾にある LF (0x0a
) を抜き、2 つの絵文字の間に ZWJ (U+200D
) を間に挟むと次のようなバイト列になります。
😵(f09f98b5
) ZWJ(e2808d
) 💫(f09f92ab
) = 😵💫(f09f98b5 e2808d f09f92ab
)
1# 末尾の 0a は LF2▶ echo "f09f98b5 e2808d f09f92ab 0a" | xxd -p -r3😵💫
1 つの絵文字の表現のために 11 byte も必要ですね。
結合文字の問題
Unicode の結合文字がうむトラブルとしては次のようなものがあるようです。
(1)同一の字体が、複数の符号位置を持つ
Unicode では、同じ字体なのに複数の表現方法が可能である文字が存在します。
例えば、「ゔ」を例にすると、以下の 2 通りの表現方法があります。
- ゔ (
U+3094
) - う + ◌ ゙ (
U+3046
U+3099
)
見かけ(字体)は同じでも、バイト列でみると確かに異なります。
1▶ echo "ゔ" | xxd200000000: e381 86e3 8299 0a .......34▶ echo "ゔ" | xxd500000000: e382 940a ....
こうした文字が混入は、テキスト検索などにおいて意図しない挙動をうむ原因となります。
この問題を避ける一般的な対処として、正規化があります。
ここでは詳しくは書きませんが、例えば、「ゔ」(U+3046
U+3099
) を「ゔ」(U+3094
) に変換して、1 つの字体に対して 1 つの符号表現しかないように変換を施すようなことをします。
(2)文字数の扱い
結合によって表現された文字を構成する要素もまた Unicode で符号位置を与えられた文字です。 これにより、我々人間には 1 文字にしか見えないものが、複数の文字で構成されるということが起きます。
例えば、絵文字 ”😵💫” U+1F635 U+200D U+1F4AB
は 3 つの独立した符号位置をもつ文字から構成されるので、3 文字として認識される場合があります。
以下に Python での例を示します。
1▶ python32Python 3.9.5 (default, May 4 2021, 03:36:27)3[Clang 12.0.0 (clang-1200.0.32.29)] on darwin4Type "help", "copyright", "credits" or "license" for more information.5>>> len("😵💫")637>>> "😵💫"[0]8'😵'9>>> "😵💫"[1]10'\u200d' # = ZWJ の Unicode 符号位置11>>> "😵💫"[2]12'💫'
まとめ
- Unicode には、複数の符号位置を足し合わせて表現可能な文字がある
- 文字数の扱いなどをめぐってプログラミング上問題となることがある