双方向テキスト (BiDi)

左→右 (LTR) と右→左 (RTL) のテキストが混在する処理。アラビア語やヘブライ語を含む多言語テキストで必要。

双方向テキスト (BiDi: Bidirectional Text) は、左から右 (LTR: Left-to-Right) に書く言語と右から左 (RTL: Right-to-Left) に書く言語が同一テキスト内に混在する状況を扱う技術です。右から左に書かれる文字体系にはアラビア文字、ヘブライ文字、ターナ文字 (ディベヒ語)、シリア文字などがあり、アラビア文字はアラビア語だけでなくペルシア語やウルドゥー語にも使われます。グローバルなサービスを提供する Web サイトでは、BiDi 対応は避けて通れない課題です。

Unicode Bidirectional Algorithm (UBA) は、テキスト内の各文字の方向性を自動判定し、正しい表示順序を決定します。仕様は Unicode Standard Annex #9「Unicode Bidirectional Algorithm」で定義されています。各文字には方向の型が割り当てられており、「強い」型 (ラテン文字は L、ヘブライ文字は R、アラビア文字・ターナ文字・シリア文字は AL)、「弱い」型 (欧文数字やアラビア数字、数字の区切り記号)、「中立」型 (空白やそれ以外の記号) に分かれます。RTL の文字体系でも数字は左から右へ書くため、アラビア語やヘブライ語の文章は本質的に双方向になります。

弱い型と中立型の文字は周囲の文脈で扱いが変わるため、数字と記号が混ざる箇所で見た目が崩れます。区切り記号が数字の一部として扱われるのは両側を数字に挟まれている場合だけで、そうでなければ中立文字になり、周囲の RTL に引かれて位置が動きます。RTL の文章に「123, 456, 789」と書くと、数字の並び自体は左から右のまま保たれますが、3 つの数字は右から左の順に並び、カンマは各数字の左側に付いて「789 ,456 ,123」のように表示されます。電話番号の先頭に付けた国番号の + も数字に挟まれていないため中立文字となり、表示上は番号の反対側に回ってしまいます。数字を含む文字列をそのまま RTL の文中に置くのが危険なのは、この挙動が原因です。

HTML では dir="rtl" 属性で方向を明示し、<bdo> (Bidirectional Override) 要素で並び順を強制します。ユーザーが入力した名前や検索語のように方向が事前に分からない文字列は、<bdi> 要素で囲むか dir="auto" を付けて、最初に現れた強い方向の文字から方向を決めさせるのが定石です。囲まないと、RTL の名前の直後にある句読点や括弧が名前側に引き寄せられ、文全体の並びが崩れます。CSS の direction プロパティや論理プロパティ (margin-inline-startpadding-inline-end など) も BiDi 対応に重要です。物理プロパティ (margin-left) の代わりに論理プロパティを使うことで、LTR/RTL の切り替えに自動対応できます。

マークアップが使えない場面 (プレーンテキストの通知文、CSV、コミットメッセージなど) では、Unicode の方向制御文字で同じ制御ができます。分離用の制御文字は U+2066 LRI (dir="ltr" 相当)、U+2067 RLI (dir="rtl" 相当)、U+2068 FSI (dir="auto" 相当) で、いずれも U+2069 PDI で閉じます。古い埋め込み用の U+202A LRE / U+202B RLE (閉じは U+202C PDF) より分離用を使うことが Unicode 標準で推奨されています。ただし制御文字は段落の境界を越えないため、文書全体の既定方向はマークアップ側で指定する必要があります。判断基準は単純で、マークアップが使えるなら属性、使えないなら制御文字です。

BiDi テキストはセキュリティ上の懸念もあります。2021 年に報告された Trojan Source 攻撃では、Unicode の方向制御文字 (RLO、LRI など) を悪用してソースコードの見た目と実際の実行順序を異なるものにする手法が示されました。オーバーライド用の制御文字 (U+202D LRO、U+202E RLO) や分離用の制御文字はいずれもゼロ幅で画面に現れないため、レビューで見た目だけを信用すると気付けません。主要なコードエディタや差分表示には、これらの不可視文字を可視化したり警告を出したりする機能があります。ソースコードやファイル名を受け取る仕組みでは、方向制御文字の混入を機械的に検出する検査を入れておくと安全です。

文字数カウントにおいて、BiDi テキストは見た目の文字数と内部的な文字数が一致しない場合があります。方向制御文字 (U+200E LRM、U+200F RLM や U+2066〜U+2069 の分離用制御文字など) はゼロ幅で画面に表示されませんが、文字数としてはカウントされます。分離用の制御文字は開きと閉じで 1 対 2 文字なので、文中に差し込んだ 5 個の名前を囲めばそれだけで 10 文字増えます。投稿の上限が 140 文字であれば、見た目には 130 文字しか書いていないのに上限に達するという事象が起こります。判断基準としては、上限を判定する前に方向制御文字を除いた文字数を数え、保存や表示にはそのまま残すのが実務的な折り合いです。除去してしまうと表示の並びが崩れるため、一律に削るのは避けます。

この記事を共有