からあげ日記

Webエンジニア的なこととか。

len() で全角記号の長さが 2 になる / Unicode と variation selector

先日、こんなことがおきました。

どう考えてもおかしい気がする。

Twitter でつぶやいている ▶︎ は、Webサイト上の文章の処理中に出力された文字列をコピペしたものだったので、この文字自体がおかしいのではないかと思い調べてみることにしました。

環境

$ ./venv/bin/python3.6 --version
Python 3.6.2

調べてみた

  • text_copyed_symbol = '▶︎' : コピペした記号(ツイート内でつぶやいた記号)
  • text_triangle = '▶' : GoogleIMEで「さんかく」で変換して出て来る記号(右向黒三角)
./venv/bin/python3.6
Python 3.6.2 (default, Jul 17 2017, 16:44:45)

>>> text_copyed_symbol = '▶︎'
>>> len(text_copyed_symbol)
2
>>> text_triangle = '▶'
>>> len(text_triangle)
1

なんかおかしい。

>>> import unicodedata
>>> text_copyed_symbol.encode('unicode-escape')
b'\\u25b6\\ufe0e'
>>> text_triangle.encode('unicode-escape')
b'\\u25b6'
>>>
>>> b'\\u25b6\\ufe0e'.decode('unicode-escape')
'▶︎'
>>> b'\\u25b6'.decode('unicode-escape')
'▶'

\\ufe0e 誰やお前。

バイト文字に変換してみる。

>>> bytes(text_copyed_symbol, 'utf-8')
b'\xe2\x96\xb6\xef\xb8\x8e'
>>>
>>> b'\xe2\x96\xb6'.decode('utf-8','strict')
'▶'
>>> b'\xef\xb8\x8e'.decode('utf-8','strict')
'︎'

どうやら \xef\xb8\x8e は VARIATION SELECTOR のようです。

Unicode/UTF-8-character table - starting from code position FE00

U+FE0E ︎ \xef\xb8\x8e VARIATION SELECTOR-15

variation selector をつけることで絵文字と非絵文字を区別しており、また、絵文字にスタイルを付与しているようです。

d.hatena.ne.jp

qiita.com

そして、 \xef\xb8\x8e 自体は幅がなく、前の文字( \xe2\x96\xb6 )に結合してひとつの形になるようです。

つまりこの2文字カウントの謎の右向黒三角は、本来は右向き三角の絵文字( f:id:Karage_Ageta:20171207134217j:plain:h15 )だった、ということですね!

対処法

1. variation selector に対応しているパッケージを使う

grapheme は variation selector を考慮して文字数をカウントしてくれるようです。 github.com

2. variation selector を切り落とす(非推奨)

本当は variation selector 込で 1 文字として扱うのが一番ですが、切り落として無理やり 1 文字にするパターンです。

>>> import re
>>> r = re.compile(u'[\uFE0E\uFE0F]', re.UNICODE)
>>> r.sub(r'', text_copyed_symbol)
'▶'
>>>
>>> len(r.sub(r'', text_copyed_symbol))
1
>>> bytes(r.sub(r'', text_copyed_symbol), 'utf-8')
b'\xe2\x96\xb6'
>>> r.sub(r'', text_copyed_symbol).encode('unicode-escape')
b'\\u25b6'

参考

c4se.hatenablog.com

medium.com