UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

文字数を数える

システムで扱う文字数の数え方についての話し合いの場があった

個人的には過去の@kawasimaさんの記事を思い出した qiita.com

読み返すと物凄い情報量だったのと、今回いろいろ情報をいただいてアップデートできたので、文字数を数える ということについて改めて自分なりにまとめておく

文字数を数えるということ

aあ𩸽👨‍👩‍👧‍👦を4文字として扱いたいが、システムでこれをカウントするのは厄介という話

バイト数を数える

Shift_JISの世界では、半角文字は1バイト、全角文字は2バイトだったが、UTF-8ではそれらの文字は、1バイト~3バイトで表される

  • a は 1バイト
  • は 3バイト

そのため、Shift_JISの世界で通用したバイトでの文字長の数え方はUTF-8では通用しない

コードポイントを数える

コードポイントはUnicodeで規定されている文字に割り振られた数字のこと
コードポイントは16進数表示で頭にU+を付けて用いられる

  • aUnicodeUx61が割り振られている1文字
  • UnicodeUx3042が割り振られている1文字

ではこのコードポイントの数を数えればよいかというとそういうわけでもない

サロゲートペア

実際このコードポイントをシステムで扱うためにはbyte配列にする必要があり、UTF-8などの符号化方式を用いてエンコーディングしてから使う

しかし、UTF-8エンコーディングする場合、コードポイントが16進数で10000以上の文字は表現できない

そのため、UTF-8ではコードポイントのD800〜DBFFとDC00〜DFFFを組み合わせてこれを表現する仕組みになっている

この仕組みをサロゲートペアという

これによりUTF-8のシステムで扱う場合は2文字の扱いになってしまう

JavaScript

> "a".length
< 1
> "あ".length
< 1
> "𩸽".length
< 2

「𠮷野家」の𠮷もサロゲートペア

Qiitaの記事にもあるように、JavaJavaScriptで単にString.lengthメソッドを使って文字数精査をしてしまうと、𠮷は2文字と判定されてしまうので、3文字で入力してるのに、「3文字で入力してください」っていうエラーがでてしまうので注意が必要

結合文字

サロゲートペアの仕組みとは別に、あえてコードポイントを組み合わせてつくられている文字がある

例えば、👨‍👩‍👧‍👦 はUx1F468 Ux200D Ux1F469 Ux200D Ux1F467 Ux200D Ux1F466
それぞれ、👨 👩 👧 👦Ux200Dで表現)で JSのString.lengthでは11文字になる

人間が認識する文字(書記素)

人間が認識している1文字というのは書記素(Grapheme cluster)と呼ばれるらしい

aあ𩸽👨‍👩‍👧‍👦を4文字として数えるのは書記素を数えるということ

コードポイントから書記素を取り出したい

コードポイントが書記素にどうなるかのルールがある

UAX#29
http://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries

この仕様を元に文字を抽出すればよいが大変そう

プロジェクトで決めてしまう

それでも文字数の考え方は人によって違うかもしれないのでキリがない

なので「このシステムではこれを〇〇文字とカウントします!」と決めてしまうのが最良かも

String.lengthで数えるのが、一番シンプル

Rubyは何もしなくても"𠮷野家".length3 となる https://magazine.rubyist.net/articles/0025/0025-Ruby19_m17n.html

Tweitterと同じ数え方にするなら、twitter-textを利用するのがよいかも
https://github.com/twitter/twitter-text

UAX#29の仕様に基づいてカウントするなら
https://github.com/orisano/graphemesplit

まとめ

10文字以内の入力フォームでaあ𩸽👨‍👩‍👧‍👦を入力した場合、JSのString.lengthでカウントすると15文字になるのでエラーになるが

エラーになったらなったで、ユーザーは不思議に思うかもしれないが、「絵文字ダメなのかな?」とか「別の言葉で試してみよう」とか、ユーザー側が柔軟に対応してくれるかもしれない

aあ𩸽👨‍👩‍👧‍👦を4文字として扱うことに疲弊するかもしれないので、問題にならないならそちらに倒すのもあり

どうしてもダメなら何をもってカウントするかはしっかり決めておく必要がありそう