UAX #11では既存の実装に配慮して、文字をその占める幅によって
- Fullwidth("F"/全角)
- Halfwidth ("H"/半角)
- Wide ("W"/広)
- Narrow ("Na"/狭)
- Ambiguous ("A"/曖昧)
- Neutral ("N"/中立)(Not East Asian)
では実際にどの文字(コードポイント)がどの分類になるのか、ですが、これはUnicodeデータベースの一部としてhttps://www.unicode.org/Public/UCD/latest/ucd/EastAsianWidth.txtにリストされています。基本的にこれを何らかの形で配列化しておいて参照すればいいのですが、Unicode(UCS4)で扱えるコードポイントは最大でU+0000からU+10FFFFの1,114,112個あり、単純にテーブル化すると約1MBになってしまいます。しかしプレーン4~13は未割当で定義も存在しません(この場合はデフォルトで"N"である(
All code points, assigned or unassigned, that are not listed explicitly are given the value "N".)と明記されています)。そこで今回はプレーン毎に分割して、フルマッピングするプレーンと1つの値で代表させるプレーン、という形で領域を節約した実装を考えてみます。
まずはUAX #11で規定されている分類を列挙型として定義し、レコードヘルパでそれぞれに対応する文字幅を返すようにします。 ここでクラスプロパティTEastAsianWidth.EastAsianは"A"の扱いを決めるもので、Trueなら東アジアの組版(フォントが日本語など)、Falseなら欧文の組版(フォントが欧文)であることを示します。
次にEastAsianWidth.txtをテーブル化します。まずそれぞれのプレーンを格納するテーブルのレコード型と、このテーブルへのポインタと代表値をセットにしたレコード型を定義します。 このTPlaneの配列(0..16)を今回はEastAsianWidth.txtから生成しますが、とりあえず仮に こんな定数定義があるものとします。これを使用する形でレコードヘルパTEastAsianWidthHelperにクラスメソッドGetEastAsianWidthを追加します。 クラスメソッドGetEastAsianWidthでは、プレーン毎のテーブルがあればそこから、テーブルがなければ代表値を取得し、列挙型TEastAsianWidthとして返します。
あとはUnicodeのバージョンアップに簡単に追随できるようにするために、EastAsianWidth.txtから上記のテーブル部分を自動生成する処理を別ユニットに作っていきます。
まず読み込んだプレーン毎のデータを格納するレコード型と、関係するメソッド(初期化、値の指定、代表値だけでテーブルを省略可能かどうかの判定)を用意します。 これを使ってUCS4のコード範囲全体を扱うレコード型を用意します。 あとはEastAsianWidth.txtを解析して値を格納し、テーブル化するメソッドを用意します。 レコード型TEastAsianWidthPropertiesはサイズが大きく、デフォルトの設定ではスタックオーバフローするため、New/Disposeでヒープ上に確保するようにしています。読み込んだEastAsianWidth.txtの各行が このようになっているものを、正規表現で複数指定、単独指定のどちらかのパターンにマッチングさせて、コードポイント(の範囲)と属性を取り込んでテーブルに格納し、最後にテーブルの内容をDelphiのコードの一部として出力しています。
このConvertEastAsianWidthを使ってEastAsianWidth.txtをEastAsianWidth.incとして変換したら、上記のTPlaneの配列部分を と置き換えれば完成です。
これで前回のサンプルにチェックボックスを1つ追加して、 こんな感じで全体の文字数を得ることができるようになります。チェックボックスのチェック状態で"á̂̃̄"のWが変化するのがわかりますね。
誰ですか、絵文字が混ざると意味がないじゃないか、とかいう人は!そう、絵文字がZWJ(ゼロ幅接合子)を使ってグリフを増やすようになったあたりから、実際にレンダリングしてみないとどのくらいの表示幅になるのかはわからなくなっているのです。
最終的なプロジェクト全体をGitHubに上げてあります。今後Unicodeのバージョンが上がっても、更新されたEastAsianWidth.txtを取り込むことで最新のものに準拠することができます。
参考: 東アジアの文字幅 - Wikipedia