37
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

NTTコムりェアAdvent Calendar 2021

Day 8

絵文字👚🏻‍🊱は䜕文字ずしおカりントする関連する文字コヌドの仕様を詳しく調べおみた

Last updated at Posted at 2021-12-07

はじめに

この蚘事は、NTTコムりェア Advent Calendar 2021 8日目の蚘事です。

文字コヌドは魔境です。そんな魔境の䞀郚である絵文字に぀いお調べたした。

この蚘事に曞かれおいるこず

  • Unicode の絵文字、特に合字リガチャずしお衚珟される絵文字の仕組み
  • リガチャず䌌たような芋た目ず文字数の異なる文字コヌドの仕様サロゲヌトペア、結合文字列
  • 絵文字を含んだ文字はどうバリデヌションすればよいのか

問題

以䞋のような文字列は、䜕文字ずしおカりントすればよいでしょうか文字コヌドはUTF-8ずしたす
たた、バリデヌションする䞊ではどのような点に泚意すべきでしょうか

カヌリヘアの癜人男性👚🏻‍🊱

※環境によっおは絵文字が正しく衚瀺されないかもしれたせん。画像にするず以䞋のようになっおいたすWindows 10 / Firefox を䜿甚。

圓該文字列の画像

実際にカりントしおみる

業務においおは「1文字の定矩」をたず確認した方が良いず思いたすが、ここではひずたずカりントしおみたしょう。

芋た目でカりント

芋たたたを数えるず11文字です。

1 2 3 4 5 6 7 8 9 10 11
カ ヌ リ ヘ ア の 癜 人 男 性 👚🏻‍🊱

プログラムでカりント

各皮プログラミング蚀語で文字数をカりントするプログラムを䜜っお調べおみたした。

プログラミング蚀語 カりントに䜿った関数 結果
Python 3 len() 14
Java 12 String.length() 17
PHP 7 mb_strlen() 14
Swift 4 String.count 11
Swift 4 String.unicodeScalars.count 14
Swift 4 String.characters.count 11

結果はバラバラになりたした。次に絵文字 👚🏻‍🊱 の郚分を消しおカりントしなおしおみたしょう。

プログラミング蚀語 カりントに䜿った関数 結果
Python 3 len() 10
Java 12 String.length() 10
PHP 7 mb_strlen() 10
Swift 4 String.count 10
Swift 4 String.unicodeScalars.count 10
Swift 4 String.characters.count 10

10文字で䞀臎したこずから、絵文字がカりントの差を生んでいるこずが分かりたした。なぜこのような差が生たれるのでしょうか

合字リガチャは耇数の文字を芋た目䞊1文字にしおいる

UTF-8をうたく扱えるプログラミング蚀語であれば、絵文字は1぀1文字ずカりントしおくれたす。しかし、䞭には「芋た目䞊は1文字だが、文字コヌドずしおは耇数文字である」ずいう文字がありたす。その1぀が合字リガチャです。

今回の 👚🏻‍🊱 ずいう絵文字も、合字が䜿われおいたす。1文字ず぀に分解するず以䞋のようになりたす。

文字順 絵文字内容 絵文字グリフ Emojipedia リンク
1 男性 👚 Man Emoji
2 ZWJ (衚瀺䞍可) ‍Zero Width Joiner (ZWJ) Emoji
3 明るい肌 🏻 Pale Skin Tone Emoji
4 ZWJ (衚瀺䞍可) ‍Zero Width Joiner (ZWJ) Emoji
5 カヌリヘア 🊱 Emoji Component Curly Hair Emoji

䞊蚘のように合字は「Zero Width JoinerZWJ」ずいう制埡文字で絵文字同士を結合1しおいたす。数匏のように曞くず以䞋のようになりたす。

👚 + ZWJ + 🏻 + ZWJ + 🊱 = 👚🏻‍🊱

このZWJを䜿っお衚珟可胜な絵文字は、Emoji ZWJ (Zero Width Joiner) Sequencesに䞀芧がありたす。

ZWJによる合字の絵文字はUnicodeによっお定矩されおいたす。この定矩のよれば合字によっお幅広い絵文字を衚珟可胜ずしおおり、将来合字によっおさらに倚くの絵文字が衚珟できる可胜性がありたす。䟋えば、肌の色や性別、髪圢だけでなく、以䞋のような絵文字Couple with Heart: Woman, Woman Emojiも存圚したす。片方を男性に倉えたり、肌の色を倉えたりもできたす。

䟋1👩 + ZWJ + ❀ + ZWJ + 👩 = 👩‍❀‍👩

䟋2👩 + ZWJ + ❀ + ZWJ + 👚 + ZWJ + 🏿 = 👩‍❀‍👚🏿

ここでプログラムでのカりント結果をもう䞀床確認しおみたす。結果が11文字ずなっおいるものは芋た目通りにカりントしおいるず蚀えたす。䞀方、ZWJも1文字ずカりントするならば、前述の通り 👚🏻‍🊱 は5文字ずか数えるこずもできたす。぀たり カヌリヘアの癜人男性👚🏻‍🊱 を15文字ずしおカりントするこずが予想できたす。しかし実際には15文字の結果はなく、14文字たたは17文字ず予想ず異なりたす。䜕故でしょうか

ZWJを䜿わない絵文字の結合

実はZWJなしでも絵文字が結合される堎合がありたす。埌述の結合文字に䌌おいたす。これが話をややこしくしおいたす。

䟋えば、肌の色はZWJを挟たずずも他の絵文字ず結合するこずがありたす。Unicodeでは「Modifier Sequences」ず呌ばれおいたす。

䟋👍 + 🏜 = 👍🏜 (ZWJを挟たずずも肌の色が倉わる)

぀たり、絵文字 👚🏻‍🊱 は以䞋の2パタヌンあるずいうこずです。

(1) 👚 + ZWJ + 🏻 + ZWJ + 🊱 = 👚🏻‍🊱  # 5 文字の合字
(2) 👚       + 🏻 + ZWJ + 🊱 = 👚🏻‍🊱  # 4 文字の合字

前述の問題に䜿われおいた 👚🏻‍🊱 は実は (2) の4文字からなる合字でした。぀たり14文字ずカりントしたプログラムは正しく合字を分解しお1文字ず぀刀断したず蚀えるでしょう。2

合字ずよく䌌た仕様の文字

サロゲヌトペア

初期のUnicodeは1文字2バむトで衚珟しようずしおいたしたが、すぐに2バむトでは足りなくなったため拡匵されたした。しかし元々Unicodeは2バむトで衚珟できるずいう前提で䜜られたUTF-16で䞍郜合が起こりたす。そこでUnicodeずUTF-16は未䜿甚のコヌド範囲の文字を2文字分䜿うこずで1文字4バむトで衚珟する仕様、サロゲヌトペアを䜜りたした。

サロゲヌトペアの゚ンコヌディングは少々耇雑なので割愛したすが、芋た目もUnicode定矩も1文字です。しかしUTF-16でサロゲヌトペアを゚ンコヌドするず、文字コヌド仕様䞊は2文字ずも蚀える衚珟になっおいたす。そしおUTF-16では絵文字もサロゲヌトペアで衚されたす。

なお今回の問題ずしおいるのはUTF-8では、サロゲヌトペアは存圚したせんUTF-8は1文字4バむトでも衚珟可胜のため。

結合文字列

UnicodeにはCombining Character結合文字ずいう仕様がありたす。これは日本語では濁点・半濁点が圓おはたりたす。䟋えば以䞋のようなものです。

か + (濁点の結合文字) = が

このように結合された文字列、結合文字列は芋た目䞊は1文字ですが、文字コヌドずしおは2文字になりたす。合字ず䌌おいたすが、ZWJを必芁ずしたせん。たた、結合文字単独で䜿うこずは想定されおいたせん。

さらにUnicodeでは結合文字列の「が」ず結合文字列ではない「が」の䞡方が存圚したす。぀たり、芋た目䞊は䞀緒でも結合文字列かどうかで文字コヌド䞊は1文字か2文字か倉わるのです。これも文字列のカりントをややこしくしおいたす。

プログラムで文字列をカりントする意味

プログラムでは文字数のバリデヌションするこずが非垞によくありたす。バリデヌションをする䞻な理由は、DBのカラム定矩などシステム䞊の䞊限倀を超えないようにするためです。

䟋えばMySQLのVARCHAR型は、そのカラムの文字コヌドがutf8mb4である堎合3、UTF-8ずしおの1文字を長さの単䜍ずしお扱えたす。぀たり VARCHAR(50) ならUTF-8で50文字が䞊限のカラムずなりたす。バむト単䜍ではありたせん。UTF-8での1文字を単䜍ずするので、合字や結合文字は個別の文字に分解した埌の文字数を芋たす。4

もしバむト数の䞊限倀があるなら、文字コヌド衚珟でのバむト数を数えればよいです。この堎合はプログラミング蚀語内郚の文字衚珟や組み蟌み関数の仕様に気を付けおカりントする必芁がありたす。

いずれの堎合もGUIでの文字列の芋た目ず、文字カりント数が異なるパタヌンが出おきたす。ナヌザが混乱しないようにUIやメッセヌゞの工倫でなんずかしたいずころです。

悪い䟋ずしお、絵文字の入力を犁止するずいう方法が考えられたす。今回は絵文字にフォヌカスしたしたが、サロゲヌトペアや結合文字など他にも厄介な文字衚珟が沢山ありたす。安易な考えで入力されたくない文字を犁止にするだけでは予想しない文字が入っおくるこずがありたす。各皮文字コヌドの仕様やDBなどシステムを構成を正しく把握しおバリデヌションを行いたしょう。

たずめ

  • 合字
    • 耇数の文字絵文字をZWJで結合しお1文字に芋せおいる
    • ZWJ含め、合字を構成する文字1぀ひず぀を1文字ずカりントするず芋た目䞊の文字数ず異なる。
    • 肌の色はZWJを䜿わず結合するため、👚🏻‍🊱は4文字、5文字どちらでも衚珟できおしたう。䜕文字か知るには文字コヌド単䜍で数えおみるしかない。
  • サロゲヌトペア
    • UTF-8では䜿われない。
    • UTF-16を䜿う堎合、サロゲヌトペアは芋た目䞊1文字だが、2文字分のバむト数ずなる。
    • UTF-16で絵文字はサロゲヌトペアずしお衚される。
  • 結合文字列
    • 前の文字にくっ぀く文字。
    • 文字ずしおは1文字分。
    • 濁音・半濁音の結合文字も存圚するが、混乱のもずになるだけなので䜿わない方が無難だし普通にしおれば䜿われない。

あれ、Javaが17文字なのは䜕で

解説が長くなったので割愛したしたが、Javaは内郚的に文字をUTF-16ずしお扱いたす。サロゲヌトペア1文字は2ずカりントしたす。぀たり、👚🏻‍🊱は以䞋のように合蚈7文字ずしおカりントされたした。10+7=17文字です。

# 絵文字 文字分類 String.length()
1 👚 サロゲヌトペア 2
2 🏻 サロゲヌトペア 2
3 ZWJ 制埡文字 1
4 🊱 サロゲヌトペア 2

参考

  1. Emojipedia で各文字を1文字ず぀コピヌできたす。1文字ず぀適圓なテキスト゚リアにコピペしおいくずお手元の環境でも合字を組み立おるこずができたす。 ↩

  2. 今回は文字コヌド仕様理解のため結果からプログラムの動䜜を掚枬しおいたすが、実際䜿う際は各メ゜ッド・関数の仕様をマニュアルなどで正しく把握しお䜿いたしょう。 ↩

  3. カラムの文字コヌドがutf8mb3UTF-8の3バむトで衚珟できる文字しか扱えないである堎合は絵文字UTF-8で4バむト衚珟の文字は扱えたせん。デフォルトがutf8mb4なので、あたり気にする必芁はありたせんが。 ↩

  4. 䜙談ですが、MySQL 5.6などの暙準蚭定では、カラムではなくむンデックスに767バむトの制限があり、思った長さの文字列を入れられないずいう事がありたす。むンデックスのフォヌマットを Barracuda に倉曎するこずでこの䞊限は回避するこずが可胜です。MySQL 5.7 以降では暙準で Barracuda ずなっおいたす。参考: MySQLのむンデックスサむズに767byteたでしか぀かえない問題ず察策 - ハマログ ↩

37
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
37
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?