書記素クラスタ
人間が 1 文字と認識する最小の表示単位。複数のコードポイントで構成されることがある。
書記素クラスタ (Grapheme Cluster) とは、人間が視覚的に 1 文字と認識する最小の表示単位です。1 つの書記素クラスタは 1 つまたは複数の Unicode コードポイントで構成されます。Unicode 標準の UAX #29 (Unicode Text Segmentation) で定義されており、テキスト処理における「文字」の境界を正確に判定するための基盤となる概念です。
書記素クラスタが複数のコードポイントで構成される代表的な例として、結合文字と絵文字があります。日本語の「が」は、合成済み形式 (U+304C) では 1 コードポイントですが、分解形式では「か」(U+304B) と濁点 (U+3099) の 2 コードポイントで構成されます。どちらの形式でも、書記素クラスタとしては 1 つです。国旗絵文字 🇯🇵 は 2 つの Regional Indicator コードポイント (U+1F1EF + U+1F1F5) の組み合わせであり、家族絵文字 👨👩👧👦 は 7 つのコードポイント (4 つの人物絵文字 + 3 つの ZWJ) で構成されますが、いずれも書記素クラスタとしては 1 つです。Unicode テキスト処理の書籍で書記素クラスタの詳細を学べます。
プログラミング言語によって「文字数」の定義は異なり、これが書記素クラスタの理解を重要にしています。JavaScript の String.length は UTF-16 のコードユニット数を返すため、絵文字 1 つが 2 以上にカウントされることがあります。Python の len() はコードポイント数を返しますが、結合文字を含む場合は見た目の文字数と一致しません。正確な「見た目の文字数」を得るには、書記素クラスタ単位でのカウントが必要です。
JavaScript では Intl.Segmenter API を使って書記素単位の分割が可能です。new Intl.Segmenter('ja', { granularity: 'grapheme' }) でセグメンターを作成し、segment() メソッドで文字列を書記素クラスタに分割できます。Python では grapheme ライブラリ、Swift では String.count がデフォルトで書記素クラスタ単位のカウントを提供します。
書記素クラスタに関するよくある誤解として、「1 コードポイント = 1 文字」という前提があります。この前提は ASCII の範囲では正しいですが、結合文字、サロゲートペア、ZWJ シーケンスを含むテキストでは破綻します。テキストの切り詰め (truncation) やカーソル移動の実装で書記素クラスタを考慮しないと、文字の途中で分割されて文字化けが発生したり、カーソルが見た目の 1 文字を飛び越えたりする問題が起きます。国際化プログラミングの書籍でも書記素クラスタは重要なトピックです。
文字数カウントツールが正確であるためには、書記素クラスタを正しく扱うことが不可欠です。ユーザーが「1 文字」と認識するものを正確に 1 としてカウントすることで、SNS の投稿文字数チェックやフォームの入力制限など、実用的な場面で信頼性の高い結果を提供できます。Unicode のバージョンアップに伴い新しい絵文字シーケンスが追加されるため、書記素クラスタの判定ルールも継続的に更新されている点に留意が必要です。