Unicodeのgrapheme cluster (書記素クラスタ) 2015/10/25
Unicodeテキストを1文字ずつ分割するアルゴリズムをUnicodeの仕様として定められており、grapheme cluster (書記素クラスタ)と呼ばれる。
普通はUnicodeのコードポイント1つずつ文字が割り当てられているので、ほとんどはコードポイント1つが1文字になるのだが、たまにコードポイント2つ以上で1文字になるものもあり、 1文字ずつテキストを分解するのは意外と複雑なルールになる。
Grapheme cluster について書かれている公式のドキュメントは以下にある。
Unicode® Standard Annex #29 UNICODE TEXT SEGMENTATION
http://unicode.org/reports/tr29/
この記事は公式ドキュメントを読んで理解したことを書いたつもり。
Grapheme cluster の仕様に関する注意事項 2015/10/25
Unicodeで定められている grapheme cluster の境界は、文字の境界としてこれを使わないといけないというものではない。地域や目的によってはこれを修正したアルゴリズムを使ってもよい。
Unicode正規化との関係 2015/10/25
NFCまたはNFDで正規化をしてもこの grapheme cluster の境界は変わらない。
NFKCまたはNFKDで正規化をすると grapheme cluster の境界は変わり得る。
例えば、”ff” (U+FB00) は grapheme cluster では1文字の扱いだが、 NFKCまたはNFKDで正規化すると “ff” (U+0066 U+0066) になり、2文字になる。
Grapheme cluster の概要 2015/10/25
人間が考える “文字” はUnicodeのコードポイント1つで表現できるとは限らない。 2つ以上のコードポイントを組み合わせて構成する文字もたくさんある。
例えば
á
という文字は、人間にとって1文字と認識するが、コードポイント2つ(U+0061 U+0301)で構成される。 (ただし、 “á” は U+00E1 という1つのコードポイントでも表現できる)
Grapheme cluster の境界は、検索、正規表現でのマッチング処理、UIなどで重要である。
マウスなどでテキストを範囲指定するときには、文字単位であってコードポイント単位ではなく、このとき grapheme cluster が使われる。 U+0061 U+0301 という並びがあったときに U+0301 だけが選択できてはいけない。また、テキストエディタやワープロなどで、U+0061 U+0301 という並びの右にカーソルがある状態で、 Backspaceを押したときに U+0301 だけが削除されるのではなく、2つのコードポイントが一度に削除されるべきである。ひらがなでも “が” の濁点だけがマウスで選択できたり、右にカーソルがある状態でカーソルの左を押したら “か” と濁点の間にカーソルが表示されたりすると、びっくりするだろう。
ハングルのテキストに対して “하” (U+1112 U+1161) で検索したときに “한” (U+1112 U+1161 U+11AB) という文字はヒットしてほしくないが、 grapheme cluster を考慮せずに、単にコードポイントを比較したらヒットしてしまうだろう。
Unicodeでは legacy grapheme cluster と extended grapheme cluster という2つの grapheme cluster を定める。大抵の場合は extended grapheme cluster を使うことが推奨されている。 Legacy grapheme cluster は過去のUnicodeの仕様で互換性のために残されている。
Grapheme cluster の例 (legacy grapheme cluster と extended grapheme cluster で共通)
ã | U+0061 U+0303 | 結合文字は直前のコードポイントと繋がる |
각 | U+1100 U+1161 U+11A8 | ハングル字母は組み合わせて1文字になる |
Extended grapheme cluster の例
நி | U+0BA8 U+0BBF | インドのタミル語の文字は子音と母音を組み合わせて1文字になる |
กำ | U+0E01 U+0E33 | タイ語の文字も子音と母音を組み合わせて1文字になる |
Legacy grapheme cluster でも extended grapheme cluster でもないが、地域によっては独自に grapheme cluster を定義して1文字と扱うべき例
ch | U+0063 U+0068 | ほとんどの人にはこれは2文字に見えるだろうが、 チェコやスロバキアではこれは1文字である |
Grapheme cluster の定義で使われる文字種 2015/11/05
Grapheme cluster の境界を定義するために以下の文字種が定義されている。
CR | U+000D CARRIAGE RETURN |
LF | U+000A LINE FEED |
Control | コントロール文字、私用領域など |
Extend | 結合文字、インド・東南アジアのコードポイントの一部など |
SpacingMark | インド・東南アジアのコードポイントの一部など |
Regional_Indicator | 国旗の絵文字を構成するコード |
L | ハングル字母の初声 |
V | ハングル字母の中声 |
T | ハングル字母の終声 |
LV | ハングル音節文字のうち終声のないもの |
LVT | ハングル音節文字のうち終声のあるもの |
Any | 上記以外すべて |
すべてのコードポイントはこの文字種のいずれかに該当する。
コードポイントがどの文字種に該当するかの情報は Unicode Character Database に保存されている。
GraphemeBreakProperty.txt | Unicode Character Database | Unicode Consortium
http://www.unicode.org/Public/UCD/latest/ucd/auxiliary/GraphemeBreakProperty.txt
Control には例えば以下が含まれる。
- U+0000..U+001F のうち U+000A, U+000D 以外
- U+007F..U+009F
Extend には例えば以下が含まれる。
- U+0300..U+036F (アクセント記号などの結合文字)
- U+200C, U+200D (ZERO WIDTH NON-JOINER, ZERO WIDTH JOINER)
- U+3099, U+309A (仮名の濁点・半濁点の結合文字)
- U+FE00..U+FE0F (異体字セレクタ)
- U+FF9E, U+FF9F (半角仮名の濁点・半濁点)
- U+E0100..U+E01EF (漢字用の異体字セレクタ)
結合文字でない濁点・半濁点(U+309B, U+309C)は Extend には含まれない。
SpacingMark はインド・東南アジアの文字など多数含まれる。
Regional_Indicator は U+1F1E6..U+1F1FF で国旗の絵文字などを表すのに使われる。例えば U+1F1EF U+1F1F5 は最近のOSやブラウザでは日本の国旗を表示する。日の丸が表示されているだろうか? -> 🇯🇵
Grapheme cluster の境界定義 2015/11/05
以下のルールが grapheme cluster の定義の本体である。
上のほうが優先度が高い。
-
テキストの先頭と最後は境界になる。
-
CR の次に LF が続く箇所は境界にならない
-
Control, CR, LF の前と後ろは境界になる
-
L の次に L, V, LV, LVT のいずれかが続く箇所は境界にならない
-
LV または V の次に V または T が続く箇所は境界にならない
-
LVT または T の次に T が続く箇所は境界にならない
-
Regional_Indicator が連続する箇所は境界にならない
-
Extend の前は境界にならない
-
SpacingMark の前は境界にならない (extended grapheme cluster でのみ適用されるルール)
-
上記ルールが適用されないすべての箇所は境界になる
“ã” などのように結合文字が前のコードポイントと結合するのは上記ルール8による。結合文字は直前の文字と結合するが、直前のコードポイントが Control や改行(LF, CR)の場合はルール3により結合せずに単独で文字の扱いになる。
半角カナの濁点・半濁点も Extend に分類されるので、前の文字に結合する。前の文字と結合できないのは前の文字が Control や改行(LF, CR)の場合に限る。つまり前の文字は仮名に限らず、Control や改行(LF, CR)以外のすべての文字と結合できるので、 “a゙” みたいなのも1文字の扱いになる。
国旗の絵文字 2015/11/04
2つ以上の連続する Regional_Indicator はそれ全体で1文字の扱いになる。
Regional_Indicator は26個のアルファベットの数だけ用意されており、 2つの Reginal_Indicator のコードポイントで国コードを表すことで、国旗の絵文字を表現するために使われる。
例えば Regional_Indicator の J と P は U+1F1EF と U+1F1F5 であり、この2つを並べると閲覧環境が対応していれば日の丸の絵文字になる。
Regional_Indicator のコードポイントを連続して並べてしまうと、 grapheme cluster の定義によりそれが全体で1文字の扱いを受けてしまう。
2つ以上の国旗を連続させる場合には、全体が1文字とならないように、国旗と国旗との間に Regional_Indicator 以外のコードポイントを置く必要がある。その場合 U+200B ZERO WIDTH SPACE が便利である。 U+200B は Control に分類されている。