CSS大解剖 15日目: 「方向」

に公開

本稿は、2024年3月頃に書き溜めていたシリーズです。最後まで温存させるのが勿体ないので、未完成ですがそのまま公開します(公開日: 2025/9/27)。そのため、内容の重複や記述方針の不一致があるかもしれませんが、ご理解ください。


CSSの仕様を理解するために、1日ごとにテーマを決めて説明する企画15日目です。今日のテーマは「方向」です。

本稿で扱う方向

←→ ↑↓ その他の用語
物理的な方向
(physical direction)

(left)

(right)

(top)

(bottom)
(物理的な)幅
((physical) width)
(物理的な)高さ
((physical) height)
x, y
フロー相対方向
(flow-relative direction)
インライン開始方向
(inline-start)
インライン終了方向
(inline-end)
ブロック開始方向
(block-start)
ブロック終了方向
(block-end)
インラインサイズ
(inline size)
または論理的な幅
(logical width)
ブロックサイズ
(block size)
または論理的な高さ
(logical height)
行 (row),
列 (column)
行の方向
(line-relative direction)
行の左
(line-left)
行の右
(line-right)
行の上
(over)
行の下
(under)
レベルラン方向
(level-run direction)
(名称なし) (名称なし)
文字の方向
(character direction)
(名称なし)
フレックス方向
(flex direction)
主軸開始方向
(main-start)
主軸終了方向
(main-end)
交差軸開始方向
(cross-start)
交差軸終了方向
(cross-end)
主軸サイズ
(main size)
交差軸サイズ
(cross size)

図: それぞれの方向概念の相関

物理的な方向

ほとんどの人は画面の物理的な方向についての直感を持ち合わせているでしょうから、これについて追加の説明は不要かもしれません。しかし、せっかくなのできちんと前提を整理してみます。

CSSは主に2次元の視覚的なデバイスにおける文書のスタイルを規定します。このような視覚的なデバイスとして代表的なのが以下の2つです。

  • 液晶などのディスプレイ (PC・タブレット・スマートフォンなど)

これらは通常、机の上に横にして置かれるか、垂直に立てて使われるか、またはその中間の角度で閲覧されます。このとき、上下と左右は以下のように決められます。

  • 画面の左右 (left /right) とは、閲覧者から見た左右です。
  • 画面の上下 (top / bottom) はもう少し複雑です。
    • 横置きの場合は、閲覧者から見た奥が上で、手前が下です。
    • 縦置きの場合は、物理的な(重力に基づく)上下と一致します。

もちろん、デバイスは自身が表示される向きを認識できなかったり、制御できなかったりすることがありますから、このルールは厳密に守られるわけではありません。たとえばPCの外付けディスプレイやスマートフォン本体を回転したときには、ユーザーが自身の見やすい方向に設定を変更するのが一般的です。紙であれば、それが印刷されたときの向きに関わらず、ユーザーは自身の見やすい方向に紙を回転して利用するでしょう。

いずれにしても、2次元の視覚的なデバイスには一般に上下左右の概念があり、以降ではこのことは仮定されます。

これとは別に、CSS Speechにも物理的な方向の概念がありますが、本稿ではこれは扱いません。

書字方向の例

はじめに、次の引用を紹介します。

Each cultural community has its own language, script and writing system. In that sense, the transfer of each writing system into cyberspace is a task with very high importance for information and communication technology.

すべての文化集団は,独自の言語,文字,書記システムを持つ.それゆえ,個々の書記システムをサイバースペースに移転することは,文化的資産の継承という意味で,情報通信技術にとって非常に重要な責務といえよう.

https://www.w3.org/TR/jlreq/#purpose_of_this_document

上の主張は、それが経済的利益を目指したものであるのか、経済的利益を度外視した技術者倫理の話をしているのかを(おそらくあえて)曖昧にしていますし、もしかしたら賛同できない方もいるかもしれません。しかし、何を理想とするかはさておき、現代のWeb技術が 現実の文化と技術的制約の間で折り合いをつける形で成立してきた ということは事実であり、少なくとも現状を理解する上ではこのような背景を理解しておくのがよいと筆者は考えます。

さて、日本語圏の皆さんはよく知っていると思いますが、文字の進行方向は言語によって異なります。文字以外のコンテンツも、その影響を受けます。これには以下のような例があります。

  • 英語(ラテン文字の言語)などでは文字を左から右に書きますが、逆に右から左に書かれる文字体系も存在します。代表的なものがアラビア語(アラビア文字の言語)です。
  • 加えて、アラビア語では算用数字を左から右に書くため、同じ言語でも両方向が混在します。それだけでなく、引用などの理由で同じコンテンツの中にアラビア語と英語が混在するのも珍しい話ではありません。
  • 日本語(漢字圏の言語)などでは横書きが普及していますが、伝統的には文字を上から下に書きます。
  • モンゴル文字はモンゴル語を表記する方法のひとつです。この書記体系では、文字を上から下に書きますが、漢字圏と異なり、行は左から右に進行します。
  • 現代に使われている言語ではありませんが、下から上に書かれたとされる文字もあります。
  • 現代では使われていませんが、左から右に書き、次の行では右から左に折り返し、また次の行では左から始める書き方もあります。

これらを踏まえて、CSSにおける方向について説明していきます。

言語と文字

本稿の前提知識として、言語と文字について確認しておきます。

  • 言語 (language) は日本語や英語などのことです。
  • 文字 (script) は漢字、かな、ラテン文字、アラビア文字などのことです。書記体系 (writing system) とも言われます。文字と書記体系を区別して使うこともありますが、本稿の範囲内では厳密に区別する必要がないため、ここでは混同して使うこととします。
    • 「文字」という日本語にはcharacterやletterという意味もありますが、これらは「あ」や "A" などの個別のシンボルを指しています。scriptはこうしたシンボルの集合を指します。
    • アルファベット (alphabet) は文字 (script) の一種です。ただし、日本語で「アルファベット」と言った場合はラテン文字を指すことが多いです。
  • 同じ文字が複数の言語で使われることがあります。
    • 漢字は中国語や日本語の表記に使われます。
    • ラテン文字は英語、フランス語、ドイツ語などの表記に使われます。
    • アラビア文字はアラビア語やペルシャ語の表記に使われます。 (ペルシャ語向けのアラビア文字のことをペルシャ文字ということもあります)
  • 同じ言語が、さまざまな異なる文字で書かれることがあります。
    • モンゴル語はラテン文字・キリル文字・モンゴル文字で書かれることがあります。
    • トルコ語はラテン文字で書かれますが、かつてはアラビア文字で書かれていました。
    • 日本語は漢字とかな文字の組み合わせで書かれますが、日本語をラテン文字(ローマ字とも呼ばれる)で表記しようというローマ字運動も存在します。この表記方法は、日本語をあらわす主な手段としては普及しませんでしたが、翻字の規則としては広く使われています。

本稿で説明するような処理の中には、文字ごとの慣習に基づくものと言語ごとの慣習に基づくものの両方があります。また、本稿ではあくまで方向に限った話題を扱っていますが、多言語化の話題は非常に奥深いので、気になる人はW3Cの多言語化タスクフォースの発行する文書を読んでみるといいでしょう。

フロー相対方向

Webの文書は文字だけからなるのではなく、画像やUIコンポーネントなど多種多様なコンテンツを内包しています。各々の文化圏において自然な表示とするためには、その文字体系の進行方向に合ったレイアウトをすることが望ましいです。これを決めるのがフロー相対方向 (flow-relative direction)です。

現代の文字体系では牛耕式 (Boustrophedon)のように左右を行ったり来たりする方式はほぼ使われていないと考えていいでしょう。牛耕式ではない普通の書字方向の総称はないようですが、たとえば「行頭復帰式 (Return-to-first-column)」とでも説明できるでしょう。現代のCSSは、この行頭復帰式のシステムを念頭に置いて設計されています。言い換えると、より広い文化をサポートすることと技術的制約を天秤にかけて、牛耕式のようなマイナーな体系はこのレベルではサポートしないと(暗黙的に)判断したのだとも解釈できるでしょう。

さてこの行頭復帰式の動作を決定するためのパラメーターは、行内で文字が進む方向 (インラインベース方向 (inline base direction)) と行が進む方向 (ブロックフロー方向 (block flow direction)) の2つです。斜めに進むようなものはここでは考えないので、この制約を数学的に言い換えると「整数係数の2次の直交行列」を考えることになり、これは理論的に以下の8種類しかなく、うち6種類のみが必要であることがわかります。

  • 左から右、上から下 (LR-TB) …… ラテン文字
  • 右から左、上から下 (RL-TB) …… アラビア文字
  • 左から右、下から上 (LR-BT) …… (該当なし)[1]
  • 右から左、下から上 (RL-BT) …… (該当なし)
  • 上から下、左から右 (TB-LR) …… モンゴル文字、反時計回りの横置きアラビア文字
  • 上から下、右から左 (TB-RL) …… 漢字、時計回りの横置きラテン文字
  • 下から上、左から右 (BT-LR) …… 反時計回りの横置きラテン文字、(オガム文字)
  • 下から上、右から左 (BT-RL) …… 時計回りの横置きアラビア文字

この記法 (LR-TB) では、先にインラインベース方向 (LR) を説明し、次にブロックフロー方向 (TB) を説明しています。文字を並べるにあたって、なるべく折り返さずに行内で進行することを優先することから、インライン方向のほうが「主な方向」であると解釈しているようです。数学的には、座標に順序を入れるにあたってブロック軸の座標を優先的に比較することから、ブロック方向が主な方向であるようにも考えられますが、ここでの慣習とは異なるので注意しましょう。

たとえば、 LR-TB (left-to-right, top-to-bottom) の場合インライン方向がLTR (left-to-right) でブロック方向がTTB (top-to-bottom) であることを意味します。CSSの挙動の一部は、この方向に対して相対的に定義されます。

フロー相対方向を定義するプロパティ

フロー相対方向は、direction プロパティwriting-mode プロパティによって決定されます。

ところが、この2つのプロパティの位置づけは大きく異なります

  • direction プロパティは、形式的にはCSSのプロパティですが、本来はマークアップの一部であるべきと考えられています。CSSでプロパティを設定するのではなく、HTMLで方向を指定することが望ましいとされています。
  • いっぽう、 writing-mode プロパティは明確にスタイルを指定するための機能として存在しています。

なぜこのような差が生じるのでしょうか。それは縦書きの文字の多くが横書きで代替可能であるのに対し、RTL文字の多くがLTRで代替不可能であるという現状を反映したものであると考えられます。

  • 漢字をはじめとした縦書きの書記体系の多くは、現代では横書きでの記述も広く受容されています。したがって、同じ文書を複数の方法で表現できることになります。これは、縦書き/横書きの差異が文書そのものとは独立したスタイルであるというモデリングに繋がります。
  • いっぽう、RTL書記体系の多くは、現代においてもLTRでの記述はあまり受容されていません。つまり、文字によって、書かれる方法が決まっていることになります。これは、コンテンツそのものがレイアウトを決定するというモデリングに繋がります。

また、そもそも縦書きから横書きへの転換が広く受容されたのに対してRTLからLTRへの転換があまり起きなかった背景が何であるかという問題も興味深いですが、この問題は筆者の知識やサーベイ能力の限界をゆうに超えていますのでここでは掘り下げないことにします。

さて、 directionwriting-mode によってフロー相対方向は以下のように決定されます

writing-mode direction インラインベース方向 ブロックフロー方向
horizontal-tb ltr LR (→) TB (↓)
horizontal-tb rtl RL (←) TB (↓)
vertical-rl
sideways-rl
ltr TB (↓) RL (←)
vertical-rl
sideways-rl
rtl BT (↑) RL (←)
vertical-lr ltr TB (↓) LR (→)
vertical-lr rtl BT (↑) LR (→)
sideways-lr ltr BT (↑) LR (→)
sideways-lr rtl TB (↓) LR (→)

言い換えると、フロー相対方向の決定ルールは以下の通りです。

  • ブロックフロー方向は writing-mode の値で決定されます。
  • インラインベース方向の決定方法は writing-mode の内容により変わります。
    • writing-mode: horizontal-tb の場合は、 direction の値がそのまま使われます。
    • writing-mode: vertical-rl および writing-mode: vertical-lr の場合は、 direction: ltr が下方向で direction: rtl が上方向になるようにマッピングされます。これは、これらがそれぞれ漢字圏(等)とモンゴル文字(等)を念頭に置いたモードであり、これらの書記体系では横書き文字を時計回りに90度回転して表記するからです。
    • writing-mode: sideways-rl の場合は direction: ltr が下方向なのに対し、 writing-mode: sideways-lr の場合は direction: ltr が上方向です。これは、これらが横書き文字圏のテキスト自体を念頭に置いたモードであり、これらの文字を右または左に90度回転させて表記するよう定義されているからです。

図: writing-modeの違い

注意するべき点として、 vertical-rlvertical-lr の挙動は対称ではありません。これは、TB-LRな書記体系 (特にモンゴル文字) における慣習を反映したものです。TB-LRの慣習では、横書き文字を反時計回りではなく時計回りに回転させます。これにより、行が上方向に進行するという非直感的な結果が発生するかわりに、横書き文字の多数を占めるLTR文字がモンゴル文字の進行方向と一致するというメリットが得られます。

フロー相対方向の参照箇所

フロー相対方向はレイアウトの様々な箇所で使われます。このとき注意が必要なのは、どの要素のフロー相対方向を参照するかは利用箇所によってまちまちな点です。一般的には、要素自身のフロー相対方向を参照するか、または包含ブロックのフロー相対方向を参照します。

  • caption-side, float, clear のフロー相対な値は、包含ブロックのフロー相対方向を参照します。
  • text-align, resize のフロー相対な値は、要素自身のフロー相対方向を参照します。
  • フロー相対プロパティ は全て一律で、要素自身のフロー相対方向を参照します。プロパティによっては包含ブロックを参照したほうがよさそうなものもありますが、そうしていないのはカスケードの処理を必要以上に複雑にしないためです。
  • marginやpaddingのパーセントは包含ブロックのインライン長を参照します。

また、ルート要素 (<html> 要素) のフロー相対方向は主書記モード (principal writing mode)と呼ばれ、特別な効果があります。主書記モードの影響を受けるものとして、代表的なものにページの進行方向があります。ページは主書記モードに基づき右方向または左方向に進行します。つまり、横書きであればインラインベース方向・縦書きであればブロックフロー方向を参照します。

また、HTML文書の <body> には書記モードに関する特別な伝播ルールがあり、 <html> 要素の writing-mode / direction プロパティの使用値は <body> 要素の使用値で上書きされます。つまり、 「<html> の計算値 → <body> の計算値 → <body> の使用値 → <html> の計算値」という順番で値が決定されます。これにより、例えば以下の文書においてページは右から左に進行することになります。

<!doctype html>
<html lang="ar">
  <body dir="rtl">
  </body>
</html>

行の向き

writing-mode プロパティは、フロー相対方向とは別に、行の向き (line orientation)という概念を定義します。これは水平テキストを行内にどう流し込むかを決定するための概念で、以下の4つの方向からなります。

  • 行の上 (over) と行の下 (under)
  • 行の左 (line-left) と 行の右 (line-right)

上を定めれば、残りの3つの方向も自動的に決定されます。行の向きは4種類考えられ、そのうち3種類が実際に存在します。

  • 行の上 (over) = 物理的な上 (top) つまり0°回転
    • writing-mode: horizontal-tb の場合
  • 行の上 (over) = 物理的な右 (right) つまり時計回り90°回転
    • writing-mode: vertical-rl の場合
    • writing-mode: sideways-rl の場合
    • writing-mode: vertical-lr の場合
  • 行の上 (over) = 物理的な左 (left) つまり反時計回り90°回転
    • writing-mode: sideways-lr の場合
  • 行の上 (over) = 物理的な下 (bottom) つまり180°回転 となるケースは存在しない

「行の向き」は必ずしもブロックフロー方向と一致しません。フロー相対方向との関係には以下の2通りが考えられます。

  • 行の上 (over) = ブロック開始方向 (block-start)
    • writing-mode: horizontal-tb, writing-mode: vertical-rl, writing-mode: sideways-rl, writing-mode: sideways-lr の場合
  • 行の上 (over) = ブロック終了方向 (block-end)
    • writing-mode: vertical-lr の場合。

行の向きの概念を使うと、インラインベース方向をよりシンプルに説明できます。それは以下の通りです。

  • インライン開始方向 (inline-start) は、 direction: ltr のときは行の左 (line-left) で、 direction: rtl のときは行の右 (line-right)。
  • インライン終了方向 (inline-end) は、 direction: ltr のときは行の右 (line-right) で、 direction: rtl のときは行の左 (line-left)。

directionプロパティ

directionプロパティの用途は以下の通りです。

  • 要素やボックスのフロー相対方向を指定する。
  • unicode-bidi が指定された際に、生成される埋め込みまたは隔離の方向を指定する。

すでに説明したように、directionプロパティをCSSで直接指定するのは避け、文書側 (HTML) で指定するべきです。HTMLには(DOM上の)要素の方向性が定義されており、これは以下のように決定されます。

  • 原則として親要素の方向性を継承する。親がない場合は以下の通り。
    • 文書のルート要素の方向性は原則 ltr
    • シャドウルートの方向性は、原則としてシャドウホストから継承する。
  • ただし、 dir="ltr" または dir="rtl" が指定されている場合は、その方向性で上書きされる。
  • また、 dir="auto" が指定されている場合は、要素の内容に応じて方向が決定される。 <bdi> は、別途指定がなければ、 dir="auto" がデフォルトとして扱われる。
    • このアルゴリズムはUnicode Bidiアルゴリズムのものと同様、最初に出現する強い方向性を持つ文字 (L, R, AL) に従う。なければ ltr にフォールバックする。

要素の方向性は:dir() 擬似クラスの判定から利用できます。

HTMLのデフォルトUAスタイルシートでは、上記のルールをなぞる形でdirectionプロパティを定義しています。

  • 以下の場合は、(DOM上の)要素の方向性が (CSSの) direction プロパティの値としてセットされる。
    • dir 属性が指定されている場合
    • <bdi> 要素。
    • (DOM上の)要素の方向性が ltr であるような <input type="tel"> 要素。
  • それ以外の場合は指定なし。つまり、継承値を利用する。

双方向テキスト

双方向テキスト (bidi) とは、ひとつの段落の中に、LTRとRTL (あるいはTTBとBTT) の部分を混在させる技術のことです。たとえば、以下のテキストを考えます。

アル=フワーリズミー(الخوارزمي al-Khuwārizmī)ことアブー・アブドゥッラー・ムハンマド・イブン・ムーサー・アル=フワーリズミー(أبو عبد الله محمد ابن موسى الخوارزمي)は、9世紀前半にアッバース朝時代のバグダードで活躍したイスラム科学の学者である。

https://ja.wikipedia.org/wiki/フワーリズミー

このテキスト全体は日本語の文であるため、左から右に進行します。しかし、文中でアラビア文字が使われている箇所では、アラビア文字の規則に従い、右から左に文字を並べています[2]。テキスト中にはそのような箇所が2つ存在しています。

双方向テキストの存在はレイアウト上も無視できない影響があります。最も重要な影響はインラインボックスの行内での断片化です。断片化とは、論理的にはひとまとまりの部分 (ボックス) が、描画の都合により複数の矩形に分割される現象のことです。普通、インラインボックスの断片化は改行によって発生しますが、双方向テキストが存在する場合は同じ行内でインラインボックスが断片化することがあります。

<style>
  .target {
    font-size: 3em;
  }
  .highlighted {
    border: 4px solid #c0c;
    background-color: #eee;
  }
</style>
<div class="target">
  Lorem <span class="highlighted">ipsum لأنه ألم، يحب، يى،</span> ريد أن 
</div>

図: Bidiにより行内で断片化が起きている

双方向アルゴリズム

CSSにおける双方向テキストはUnicodeのUAX #9に基づいて描画されます。完全なアルゴリズムは仕様で説明されているため、ここでは重要なものから順に概念別で紹介することにします。

この仕様では、テキストを文字列データとしては論理的な(文字が読まれる)順番で保存・送受信し、描画時に文字種に基づいて自動で方向を決定するよう定められています。たとえば、上のテキストの一部を抜き出してコードポイントごとに並べると以下のようになります。

インデックス: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
文字: ا ل خ و ا ر ز م ي a l
a l kh w a r z m y

アラビア文字は前後の文字と接続することで形が変わるため若干わかりにくいかもしれませんが、表の最初の文字 ا が元のテキストのアラビア文字部分の右端にあり、最後の文字 ي が左端に描画されることは見てとれるのではないかと思います。

Unicode双方向アルゴリズムは、大きく以下の3ステップに分けられます

  1. まず、テキストを段落に分け、文字の向きネスト構造を決定します。これら2つをあわせて、埋め込みレベル (embedding level) (あるいは単にレベル) と呼ばれる整数値がそれぞれの文字に割り当てられます。
  2. 次に、折り返し処理により段落を行に分割します。
  3. 各行内で、レベル情報をもとに文字の並び替えを行います

このうち、ステップ1とステップ3はUnicode Bidiアルゴリズムとして定義されています。間のステップ2はCSSなどの他の規格で定義します。

この処理のポイントは、文字の並び替えは行分割の後に行われているということです。たとえば以下の2つのスクリーンショットは同じ文書を異なる横幅で閲覧したときのものですが、アラビア文字の並びの論理的な後ろ側 = 左側から順に次の行に移動していることがわかります。

図: アラビア文字の部分が折り返されない場合

図: アラビア文字の部分が折り返されている場合

このように、行分割は双方向テキストが混在していても、論理的な順序が崩れないように行われます。

文字の並び替え

文字の並び替えにおいては、テキストのネスト構造を考慮に入れる必要があります。たとえば以下の2つの文を考えます。 (⟦...⟧ はアラビア文字で書かれるべき部分を指します)

  • ⟦イドリース⟧ と ⟦ジャマール⟧ は私の友人です。
    • 表示: إدريس と جمال は私の友人です。
  • "⟦イドリースと⟧修平⟦は私の友人です⟧" と彼は言いました。
    • 表示: ⁧"إدريس و修平 هما أصدقائي"⁩ と彼は言いました。

この2つは、文字種の並びはほぼ同じで、 R(1)→L(2)→R(3)→L(4) の順で並んでいます。ところが、この2つは並び替えに関する要件が異なります。

  • 1つ目の文は、日本語(漢字・仮名)の文の構造中にアラビア文字の部分が混在しているだけです。したがって、全体は日本語(漢字・仮名)の順に合わせつつ、2箇所あるアラビア文字の並びをそれぞれ逆順にすればOKです。
  • いっぽう、2つ目の文は、日本語(漢字・仮名)の文の中にアラビア語(アラビア文字)の文が埋め込まれています。このような場合、埋め込まれている文全体がアラビア語(アラビア文字)の順に並ぶように丸ごと逆転させるのが望ましいです。もちろん、その中にさらに埋め込まれている日本語(漢字・仮名)の部分は、再度順序を逆転させる必要があります。

この違いを表現するために、Unicode Bidiアルゴリズムの計算では埋め込みレベル (embedding level)という値を扱います。レベルは0~127の整数値で、偶数はLTRテキストをあらわし奇数はRTLテキストを表します。たとえば上の例では、以下のようにレベルが割り当てられれば期待通りの表示になることになります。 (実際にそうさせる方法は後述します)

  • ⟦イドリース⟧₁ と₀ ⟦ジャマール⟧₁ は私の友人です。₀
  • "₁⟦イドリースと⟧₁修平₂⟦は私の友人です⟧₁"₁ と彼は言いました。₀

文字を並び替えるときは、「レベルN以上の文字の連続した並び」をひとまとまりにして反転します。上の例では以下の塊があることになります。

  • ⟦イドリース⟧₁ と₀ ⟦ジャマール⟧₁ は私の友人です。₀ の場合
    • ⟦イドリース⟧₁ と₀ ⟦ジャマール⟧₁ は私の友人です。₀ (レベル0)
    • ⟦イドリース⟧₁ (レベル1)
    • ⟦ジャマール⟧₁ (レベル1)
  • "₁⟦イドリースと⟧₁修平₂⟦は私の友人です⟧₁"₁ と彼は言いました。₀ の場合
    • "₁⟦イドリースと⟧₁修平₂⟦は私の友人です⟧₁"₁ と彼は言いました。₀ (レベル0)
    • "₁⟦イドリースと⟧₁修平₂⟦は私の友人です⟧₁"₁ (レベル1)
    • 修平₂ (レベル2)

文字の形状の決定

現代のアラビア文字は、RTLである点の他に、常に筆記体(cursive script) が使われる点も特徴的です。このような文字では、文字の形状は前後の文字にあわせて変化します。この判定は並び替え後の順序で判定されます。もし何らかの理由で、本来の順序に反してアラビア文字が左から右に並べられた場合でも、見た目に基づいて文字の形が変化することになります。また、異なるレベルの文字同士は接続しません。

もうひとつ重要なのが、鏡像グリフ (mirrored glyph) の処理です。たとえば以下の文を考えます。

⁧عَدِي شامير (بالعبرية: עדי שמיר) وتلفظ آدي شامير (مواليد 6 يوليو 1952، تل أبيب، إسرائيل) هو عالم تعمية إسرائيلي وهو أحد مخترعي خوارزمية آر إس إيه (مع رونالد ريفست وليونارد أدليمان Leonard Adleman)، وشارك أيضاً في اختراع نظام تحديد فيج-فيات-شامير (بالإنجليزية: Feige-Fiat-Shamir Identification Scheme)‏ مع أورييل فيج Uriel Feige وعاموس فيات Amos Fiat. وهو أحد الرواد في مجال تحليل التعمية التفاضلي وقدم مساهمات عديدة في مجال التشفير وعلوم الحاسب الآلي.⁩

https://ar.wikipedia.org/wiki/عدي_شامير

このテキストには括弧 ( ) が登場しますが、このとき (開き括弧)閉じ括弧としての意味を持ち[3]、RTLテキスト中でも意味的に開き括弧・閉じ括弧として運用するよう定められています。

最初の数文字を論理的な順序で並べると、以下のようになります。 (書記素クラスター単位で並べています)

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
عَ دِ ي ش ا م ي ر ( ب ا ل ع ب ر ي ة : ע ד י ש מ י ר )
a d y sh a m y r b a l a b r y (h) a d y sh m y r

上で抜き出した部分は、全て右から左に並べて書かれます。 (括弧中にはアラビア文字のほかにヘブライ文字もありますが、これも右から左に書かれる文字です) このとき、10文字目の ( と 29文字目の ) はそれぞれ開き括弧/閉じ括弧として表示する必要がありますが、そのままの字形で表示すると本来の意図と逆に解釈されてしまいます。

そこで、Unicodeでは、RTLテキスト内で鏡像表示するべき文字の一覧 (Bidi_Mirrored Property) を提供しています。これは主に、括弧類と数学記号で構成されています。[4]

また、CSSの場合は関係ありませんが、既定のフォントをそのまま表示するしかないような環境ではかわりの方法が用意されています。それは、対応する開き括弧と閉じ括弧を入れ替えて表示するというものです。たとえば上の例では ( の鏡像の描画をすべきところで ) の正像を描画させ、逆に ) の鏡像を描画すべきところで ( の正像を描画させることでも同じ結果を達成することができます。この処理のために使えるデータ (Bidi_Mirroring_Glyph Property) もUnicodeで定義されていますが、対応する鏡像文字がない場合もあるので注意が必要です。

1つの記号が自動で鏡像になるのではなく、単に鏡像形が別の文字として提供されるパターンもあります。たとえば、アラビア文字テキスト中の疑問符には U+003F QUESTION MARK (?) ではなく U+061F ARABIC QUESTION MARK (؟) を使います。[5]

レベルの決定 (基本)

文字の埋め込みレベルは、文字種からの自動的な推論と専用の文字による明示的な指定の2種類の情報をもとに決定されます。まずは、文字種からの推論を簡略化した基本のルールから説明します。

レベルの基本的な決定ルールにおいては、文字種は以下の4種類に分類されます。 (これはBidi_Class Property を簡略化したものです)

  • 強い方向性のある文字
    • L: 左から右に書く文字。ラテン文字など
    • R: 右から左に書く文字。アラビア文字など
  • 弱い方向性のある文字
    • AN/EN: RTLテキスト中の数字。右書きのテキストでも、数字は左から右に書く。
  • 中立
    • N: どちらの方向にも書ける文字。ピリオドや疑問符など。

さらに、段落のレベルは0 (LTR) または 1 (RTL) として与えられるものとします。このとき、以下の手順でレベルを決定します。

  1. まず、Nに分類された文字を周囲の文字種に基づいてLかRに再分類します。たとえば、英文中の記号はLに分類されることになります。
  2. 段落の方向と同じ方向の文字には、段落と同じレベルを設定します。段落の方向と異なる方向の文字には、それより1大きいレベルを設定します。
  3. LTRな段落中では、 AN/ENに分類された文字 (数字) のレベルを2増やします。

手順3で数字のレベルを底上げしているため、数字が間に挟まっても周囲のテキストの順番は影響を受けないことになります。たとえば以下の2つのテキストを考えます。

  • لدي تفاحة (私は林檎を持っています)
  • لدي 1,000,000 تفاحة (私は1,000,000個の林檎を持っています)

このテキスト中の数字自体は左から右に書かれますが、アラビア文字との関係では、あたかもアラビア文字のテキストの一部であるかのように振る舞うため、この2つのテキスト内のアラビア文字は同じ順番に並びます。この意味で、これらの文字は「弱い」方向性を持った文字と呼ばれます。

レベルの決定 (マーク)

マークとは、Bidi用に用意された以下の3つのUnicode文字です。

  • U+200E LEFT-TO-RIGHT MARK (LRM) ... ダミーの強いLTR文字
  • U+200F RIGHT-TO-LEFT MARK (RLM) ... ダミーの強いRTL文字 (非アラビア文字として動作)
  • U+061C ARABIC LETTER MARK (ALM) ... ダミーの強いRTL文字 (アラビア文字として動作)

これらは、Bidiアルゴリズム上で強い方向性を持つ文字として扱われる以外は、空文字列と同じように動作します。つまり、文字の見た目や折り返し位置などに直接的に影響を与えることはありません。

マークは後述する特殊文字に比べて使い方もシンプルであり、レイアウトを整えるための第一選択肢として利用することができます。たとえば以下の文を考えます。

أنا طالب.

この文はLTRな段落の一部として表示されているため、ピリオドが左側に来てしまっています。そこで、文の最後にALMを置くと、以下のような自然な表示になります。

أنا طالب.؜

この場合、ピリオドはアラビア文字 ب とALMに挟まれることになります。強い方向性を持つ文字に挟まれた中立文字はその方向に変換されるため、この場合のピリオドはRTL文字として扱われることになり、意図した通りの表示が得られます。

レベルの決定 (隔離)

隔離 (isolate) という機能を使うことで、テキストのある一部分を必ず1つの塊として処理するように指定することができます。たとえばテキスト中に引用がある場合、その部分を隔離することでより意図に近い形で表示することができます。

隔離を作るには、専用のUnicode文字を使います。

  • 隔離の開始位置には、以下のいずれかの文字を置きます。
    • U+2066 LEFT-TO-RIGHT ISOLATE (LRI) ... LTR方向の隔離を作る。
    • U+2067 RIGHT-TO-LEFT ISOLATE (RLI) ... RTL方向の隔離を作る。
    • U+2068 FIRST STRONG ISOLATE (FSI) ... 中のテキストに応じて自動的に向きを判定する。
  • 隔離の終了位置には、 U+2069 POP DIRECTIONAL ISOLATE (PDI) を置きます。

これらは開き括弧と閉じ括弧のようにマッチングされ、その区間が隔離されます。隔離された区間のレベル計算は以下のように行います。

  1. 区間の外側のレベル計算時は、隔離された区間をダミーの文字で置き換えておく。このとき、ダミーの文字は中立的とみなす。
  2. その後、各区間をそれぞれ独立した段落とみなしてレベルを計算する。ただし、隔離された区間のベースレベルは周囲よりも深くなるように計算する。

たとえば、以下の例文を考えます。 (⟦⟧内は本来はアラビア文字で書かれているとします)

"⟦イドリースと⟧修平⟦は私の友人です⟧" と彼は言いました。

この例を意図通りに表示させるには、以下のように隔離区間を設定します。 (⟦RLI⟧ と ⟦PDI⟧ は対応するUnicode文字を指すとします)

⟦RLI⟧"⟦イドリースと⟧修平⟦は私の友人です⟧"⟦PDI⟧ と彼は言いました。

この場合、レベル計算は以下の2ステップに分けて行われます。

  • ⟦(ダミー区間)⟧ と彼は言いました。 というテキストを、ベースレベルを0として計算する。
  • "⟦イドリースと⟧修平⟦は私の友人です⟧" というテキストを、ベースレベルを1として計算する。

結果として、テキストは以下のように表示されます。

⁧"إدريس و修平 هما أصدقائي"⁩ と彼は言いました。

レベルの決定 (埋め込みとオーバーライド)

埋め込み (embedding) は、隔離と同じような目的で使われる仕様です。隔離は2013年にUnicode 6.3で追加された比較的新しい仕様なのに対して、埋め込みはそれよりも昔から実装されている古い機能です。

埋め込みも隔離と同様、専用のUnicode文字を使うことで導入されます。

  • 埋め込みの開始を指定する文字
    • U+202A LEFT-TO-RIGHT EMBEDDING (LRE) ... LTRテキストの埋め込みを開始する。
    • U+202B RIGHT-TO-LEFT EMBEDDING (RLE) ... RTLテキストの埋め込みを開始する。
    • U+202D LEFT-TO-RIGHT OVERRIDE (LRO) ... LTRテキストのオーバーライド埋め込みを開始する。
    • U+202E RIGHT-TO-LEFT OVERRIDE (RLO) ... RTLテキストのオーバーライド埋め込みを開始する。
  • 埋め込みの終了を指定する文字
    • U+202C POP DIRECTIONAL FORMATTING (PDF) ... 埋め込みを終了する。

これらは開き括弧と閉じ括弧のようにマッチングされ、その区間が埋め込みとして設定されます。

埋め込みは隔離と同じような目的で使われますが、以下のような問題があります。現在は隔離機能があるため、埋め込みを使う理由はあまりないでしょう。

  • 埋め込まれたテキストと埋め込み側テキストの相互作用が完全には排除されていないため、非直感的な挙動をすることがある。
  • Bidiにはいくつかのセキュリティー上のリスクがあるが、中でもRLOには大きな影響がある。

レベルの決定 (括弧)

Bidiアルゴリズムは括弧を特別にハンドルします。そこで例として、以下のテキストを考えてみます。

Al-Khwarizmi (al jabr を著した) はアラブの科学者である。

このテキストをアラビア文字を使って書いたとき、次のような結果になります。 (ここでは、括弧の特別処理がないとどうなるかを示すためにあえて対応関係にない括弧を使っています。)

الخوارزمي (الجبر を著した] はアラブの科学者である。

おそらく、上記のレンダリングでは括弧の向きがおかしくなっているのではないかと思います。これは、括弧の向きを前後の文字から推定する過程で、開き括弧と閉じ括弧の進行方向が逆になってしまうことで起きています。

実際には、Unicode 6.3以降のBidiアルゴリズムでは括弧を特別扱いしています (Bidi_Paired_Bracket および Bidi_Paired_Bracket_Type Property)。具体的には、対応する括弧には同じ方向を割り当てるような処理が挟まっています。これにより、きちんと対応する括弧を使うと以下のように表示されます。

الخوارزمي (الجبر を著した) はアラブの科学者である。

ただし、この処理は規定の括弧が適切に対応している場合にのみ反応します。また、これより前のステップで括弧の向きが確定している場合は処理の対象にならない場合があります。

レベルの決定 (文字種別)

最初にBidi_ClassをL, R, AN/EN, Nの4種類に分けて簡易的に説明しましたが、実際のBidi_Classはもう少し複雑です。

まず、Bidi上で一部の数字は弱い方向性を持ちますが、これは2種類に分けられます。

  • EN (European Number) には以下が含まれます。これらはLTRテキストとRTLテキストの両方で使われるため、文脈によって異なる扱いを受けます。
    • アラビア数字[6]: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    • 拡張インド数字: ۰, ۱, ۲, ۳, ۴, ۵, ۶, ۷, ۸, ۹ (ペルシャ語圏などで使われるもので、アラビア語で使われるものとは異なる)
    • コプト・エパクト数字
  • AN (Arabic Number) には以下が含まれ、より R に近い扱いを受けます。 (ただし、これ自体はLTR方向に書かれます)
    • インド数字: ٠, ‎١, ‎٢, ‎٣, ‎٤, ‎٥, ‎٦, ‎٧, ‎٨, ‎٩ (アラビア語で使われるもの)
    • ハーニフィー・ロヒンギャ数字: 𐴰, ‎𐴱, ‎𐴲, ‎𐴳, ‎𐴴, ‎𐴵, ‎𐴶, ‎𐴷, ‎𐴸, ‎𐴹
    • ルーミー数字
  • なお、全ての数字がENまたはANに分類されているわけではありません。たとえば、デーヴァナーガリー数字 (०, १, २, ३, ४, ५, ६, ७, ८, ९) は L に分類されます。

Bidiアルゴリズム上でENとANが区別されるのは以下の2箇所です。

  • ひとつは、ENのLへの同化ルールです。LにENが後続する場合は、そのENはLとして扱われます。これにより以下の影響が生じます。
    • LTRな段落で文字が L-EN-R のように並んだとき、 EN は R 側ではなく L 側にグルーピングされます。たとえば、 Happy 2024 سعيدةHappy ١٤٤٥ سعيدة では数字の表示される位置が異なります。
    • 隣接する中立文字への影響。ENやANは、隣接する中立文字をRに変更させるような動作をしますが、ENがLに同化するとこの効果は消滅します。
  • もうひとつは、分離記号終端記号の同化ルールです。ENには対応する分離記号 (ES) と終端記号 (ET) が定義されており、これらはENと隣接する場合にのみ数字に準ずるものとして扱われます。
    • なお、ES/ETのアラビア数字版はありません。アラビア数字と結合する記号ははじめからANに分類されています。

また、ENのRへの同化ルールでは、ENがアラビア文字に後続する場合にのみENをANに変換するようになっていて、これにより分離記号と終端記号の挙動が変わります。この挙動のために、Bidi_Class Propertyでは強いRTL文字を R と AL の2種類に細分しています。たとえば、アラビア文字とヘブライ文字では以下のような挙動の違いがあります。

  • アラビア文字の場合: وهذا صحيح 100%.؜[7]
    • 100 がANに変換されるので、ETである % は中立文字となる。ここではテキスト全体をRTLとして表示させているので、 % は R 扱いとなり、 100 より後(右側)に表示される。
  • ヘブライ文字の場合: זה נכון ב-100%.‏
    • 100 はENのまま処理されるので、ETである % はENに変換される。数字は左から右に書かれ、 % はその一部として扱われるため、 100 の左側に表示される。

ETとESの違いは、ETが終端 (数字の両側) で数字に結合するのに対し、 ESは数字の間に置くと数字に結合するというものです。

ET/ESはENにのみ結合しますが、CSに分類されたものはEN/ANのいずれの数字に対しても分離記号として動作します。たとえば、以下の例文では全て、カンマが数字の一部として認識されています。これは、カンマがCSだからです。

  • لديك 123,456 دولارًا.؜
  • لديك ١٢٣,٤٥٦ دولارًا.؜
  • יש לך 123,456 דולר.‏

ET/ES/CSは、上記のルールで数字として扱われたもの以外は、中立として扱われます。

その他に以下のようなクラスがあります。

  • NSM (Nonspacing Mark) は結合文字などが所属するクラスで、先行する文字と同じ分類を受けます
  • BN (Boundary Neutral; 境界中立) は、Unicode制御文字や予約された文字、特殊な機能を持つ文字(ソフトハイフンなど) が所属するクラスで、Bidiアルゴリズム上は空文字列と同等として扱われます。たとえば、 hy<SHY>phen (<SHY> はソフトハイフン文字とする) を表示する場合は、 hyphen と同じように計算されるため、 hy<SHY>phen 全体がひとつのLTRセグメントとして認識されます。もしBN文字を表示する必要がある場合は、前後の文字の表示方法を参考に表示方法を決定することになります。
  • B (Paragraph Separator; 段落分離記号) とは、端的に言えば改行のことです。改行で区切られた部分はBidiアルゴリズム上は「段落」と呼ばれ、Bidi処理は独立して適用されます。改行文字自体は手前の段落内にある中立的な文字として扱われますが、その埋め込みレベルは最後にリセットされます
  • S (Segment Separator; セグメント分離記号) とはタブのことです。タブは中立的な文字ですが、その埋め込みレベルは最後にリセットされます。これは以下の結果をもたらします:
    • 括弧や隔離開始・終了など、ペアで動作する文字の対応関係はタブを越えて認識される。
    • タブの前後にある文字の方向性が、タブを越えた反対側の文字の方向性に影響を与えることがある。
    • しかし、最終的にタブで区切られた部分はそれぞれ独立に、段落の方向と同じ順に表示される。
  • WS (Whitespace; 空白) は名前の通り、改行とタブ以外の空白です。空白は中立的な文字ですが、段落末尾の空白の埋め込みレベルは最後にリセットされます。これにより、末尾空白は必ず段落の終端側に配置されます。
  • ON (Other Neutrals; その他の中立文字) は名前の通り、普通の中立文字です。

Bidi_Classの遷移関係を示したのが以下の図です。

図: Bidi_Classの遷移関係

Bidiとインラインオブジェクト

テキスト中にテキスト以外のオブジェクト (HTMLにおける <img> など) が入る場合、Bidiアルゴリズム上はU+FFFC OBJECT REPLACEMENT CHARACTERと同等、つまり中立的な文字として扱われます。

CSSにおける双方向テキスト

段落

UnicodeのBidiアルゴリズムを適用するためには、まず段落の概念を定義する必要があります。HTML/CSSにBidiアルゴリズムを適用するための段落の概念は以下のように決められます。

Unicodeの規定に基づき、ある段落内の文字は他の段落のBidiアルゴリズムには影響を与えません。ただし、後述するunicode-bidiプロパティはCSS側で段落区切りの処理を行っている箇所があるため、その挙動には注意が必要です。

unicode-bidi プロパティ

CSSでBidiアルゴリズムの挙動を制御するために存在しているのが、unicode-bidi プロパティです。ただし、directionプロパティと同じく、unicode-bidiプロパティは本来はマークアップで実現されるべき機能であり、CSSによる直接指定は推奨されていない点に注意が必要です。

unicode-bidiプロパティが指定されると、その要素の前後にBidi制御用の文字が挿入されたかのように振る舞います。挿入される文字は以下の通りです。

  • unicode-bidi: normal;: 挿入なし
  • unicode-bidi: embed;: [LREまたはRLE] ... [PDF]
  • unicode-bidi: isolate;: [LRIまたはRLI] ... [PDI]
  • unicode-bidi: bidi-override;: [LROまたはRLO] ... [PDF]
  • unicode-bidi: isolate-override;: [FSI] [LROまたはRLO] ... [PDF] [PDI]
  • unicode-bidi: plaintext;: [FSI] ... [PDI]

このうち embed / isolate / bidi-override / isolate-override が挿入する文字は、その要素の direction プロパティに依存して決まります。したがって、インライン要素であっても unicode-bidi 値を持つ要素であれば direction プロパティが意味を持つことになります。

HTMLによるbidi制御

前述の通り、unicode-bidi プロパティの直接指定は推奨されません。かわりにHTMLでbidiを制御することができます

  • いくつかのタグには自動的に unicode-bidi: isolate; が設定されるため、その部分は周囲の方向とは関係なく、その要素のdirに基づいて判定されます。これには、デフォルトでブロック要素として表示されるタグの多くに設定されます。また、 <bdi> もこの効果を発生させます。
  • dir 属性が設定された要素 (ltr, rtl, または auto) にも unicode-bidi: isolate; が設定されます。ただし、以下の例外があります:
    • テキスト入力用のフォームコントロールに dir="auto" を設定した場合は unicode-bidi: plaintext; が設定されます。
  • <bdo> には unicode-bidi: isolate-override; が設定されます。

ただし、ヘブライ文字の8bit文字コードの仕様である ISO-8859-8 で書かれた古い文書の中には、ヘブライ文字をあえて逆順に記載しているものがあるらしく、こうした文書に対応するためのハックとして、エンコーディングがISO-8859-8の場合には異なるスタイルシート (文書全体がLTRで上書きされる) を適用するよう規定されています。 (これは本稿執筆中の2024年3月時点では規定と実装が残っていますが、かなり特殊なハックであるため、今後消滅する可能性も考えられます)

unicode-bidi と改行

unicode-bidi によって生成される制御用の文字は、その要素の開始・終了位置だけではなく、要素内の強制改行位置でも生成されます

テキストによるbidi制御

文書内のテキストにbidi制御用の文字がある場合、それらの文字はそのままbidiアルゴリズムに渡されます。そのため、テキストにbidi制御用の文字を書くことでもbidiの挙動を調整することができます。ただし、以下の2点に注意が必要です。

  • 改行の自動処理はunicode-bidi に由来する制御用文字にのみ適用されます。そのため、テキストで指定したbidi制御は改行で強制的に終了されてしまいます。
  • 上記以外では、 unicode-bidi が挿入する制御用文字と元からある制御用文字はbidiアルゴリズム上は区別されません。そのため、制御を開始する文字と終了する文字の対応が取れていない場合、意図しないような形での相互作用が発生する可能性があります。

なお、マーク文字であるLRM, RLM, ALMはCSSやHTMLで指定することはできませんが、これらの挙動はシンプルで上記のような注意も必要ないため、テキスト中で積極的に使ってよいでしょう。

インラインオブジェクト

UnicodeのBidiアルゴリズムは、インラインオブジェクトを U+FFFC OBJECT REPLACEMENT CHARACTERで置換したものとして扱うよう規定しています。しかし、CSSにおいてはこの例外があります。それは、これらのオブジェクト自体に特定の unicode-bidi が設定されている場合です。

具体的には、 unicode-bidi: embed; または unicode-bidi: bidi-override; が指定されている場合は、 direction の値に応じてLRMまたはRLMで置換した場合と同等として扱うよう定められています。これは、置換文字であるU+FFFCを LRE/RLE/LRO/RLO ... PDF で囲んだのとおおよそ同等なので、そのように考えてもいいでしょう。

レベルラン

同じレベルの文字の連続した並びを レベルラン (level run) といいます。CSSにおいて、レベルランはそれぞれ独立したボックス断片を形成します。

たとえば以下の例では3つのレベルランが形成され、それぞれのレベルは2, 0, 1です。1文字でもレベルランを形成すること、また周囲と同じ方向性でもレベルが違えば異なるレベルランを形成することが読み取れます。

<!doctype html>
<style>
  .target {
    font-size: 3em;
  }

  .target-span {
    border: 3px solid blue;
    box-decoration-break: clone;
  }
</style>
<p class="target"><span class="target-span">٩( 'ω' )و</span></p>

縦書きと双方向テキスト

Unicodeの双方向テキストは横書きテキストのための技術であるため、その用語や概念は横書きを念頭に置いて定義されています。しかし、CSSでは、縦書きテキストにもbidiアルゴリズムを適用します。このときは、bidiアルゴリズムの概念はCSSにおける物理的な方向ではなく、行の向き (over, under, line-left, line-right) に対応するものとして扱います。

つまり、多くの場合ではLTRをTTB, RTLをBTTとして解釈しますが、 writing-mode: sideways-lr の場合のみLTRをBTT, RTLをTTBとして解釈します。

文字の向き

もっぱら横書きで使われる文字はしばしば、横書きに特化した形状をしています。このような形状にはたとえば以下のような例があります。

  • 横方向に情報を詰められるように、縦に長い形をしている。 (半角の A など)
  • 文字の進行方向に依存した意味を持つ文字である。 (em dash など)
  • 左右の文字と繋がるように字形が変化する。 (アラビア文字など)

このような背景から、縦書きモードにおいては、単に横書きの文字を縦に並べるだけでは済まず、歴史的にいくつかの特別処理が行われてきました。逆に、縦書き文字を横に書くときにも同様に特別処理が行われてきました。これは大きく、以下の4種類に分けられます。

  • 直立 (upright): 横書きと縦書きで、文字を同じ方向に向けて描画する。 (漢字など)
  • 横置き (sideways): 文字を90°倒して描画する。
    • 本来は横書きの文字を、縦書き時は90°倒して描画する。 (ラテン文字など)
    • 本来は縦書きの文字を、横書き時は90°倒して描画する。 (モンゴル文字など)
  • 横書き用と縦書き用で、異なる字形を明示的に提供する。
    • 直立を基本とした特殊変形 (仮名文字における拗音「ぁ」など)
    • 横置きを基本とした特殊変形 (波線「~」など)

このうち、モンゴル文字のように「縦書き文字を、横書き時は90°倒して描画する」ケースでは、横に倒したときの形がUnicodeの例示字形として使われ、フォント上でもこの形で字形が定義されています。このときの回転方向は、「ラテン文字とモンゴル文字を、両方横書きにしたとき」の挙動が「ラテン文字とモンゴル文字を、両方縦書きにしたとき」のタイポグラフィー上の慣習と一貫するような方向に設定されるため、実はモンゴル文字は、ラテン文字と同じように処理できることになります。逆に、これに該当しないようなマイナーな縦書き専用文字は現在のUnicodeやCSSの枠組みからはどうしても外れてしまいます。このことはUnicodeやCSSの文書にもしばしば言及されています。

以上のように、現在のUnicodeとCSSでは、文字の向きは原則として直立横置きのいずれかであるとして整理できるようになっています。これは言い換えると以下のようになります。

  • 直立とは、文字の上方向が、物理的な上方向 (top) を向くように描画することである。
  • 横置きとは、文字の上方向が、行の上方向 (over) を向くように描画することである。
    • ただし、モンゴル文字のような縦書き専用文字における「上」はUnicode例示字形における方向を指すものとする。

文字の向きの決定方法

文字の向きが重要になるのは、タイポグラフィックモードが縦書きモードのときだけです。タイポグラフィックモードが横書きモードの場合は、文字の向きは以下のように決まります。

  • writing-mode: horizontal-tb の場合は、文字の向きの問題はそもそも存在しない。
  • writing-mode: sideways-rl または writing-mode: sideways-lr の場合は、全ての文字を横置きとする。

いっぽう、縦書きのタイポグラフィックモードを持つ場合には、 text-orientation プロパティ の値が参照されます。

  • text-orientation: mixed の場合は、UTR #50 で定義されているVertical_Orientationプロパティに基づいて直立か横置きかを決定する。特殊字形が利用可能であればそれを使う。
  • text-orientation: upright の場合は、全ての文字を直立させ、特殊字形が利用可能であればそれを使う。
  • text-orientation: sideways の場合は、全ての文字を横置きとする。

さて、現代のコンピューターに入っているフォントの多くは細部までよく調整されており、私たちはそのおかげで快適にテキストを読むことができています。テキストを読みやすくするために重要なことのひとつは隣接する文字同士の関係を考慮して最適なレンダリングをすることですが、横書きで最適な文字であっても縦書きでは文字同士の位置関係が全く異なるため再調整が必要になります。そこで、CSSでは直立モードでの描画について以下のように規定しています。

  • 縦書きの場合についての情報がフォントにあれば、それを利用します。なければ、横書きの情報から推定します。
  • アラビア文字のように、隣り合う文字の種類によって字形が変わる文字については、1文字[8]ずつ孤立形で描画します。

text-orientationは個々のテキストに適用されるため、レイアウトの結果を微調整したければインライン要素単位で適用することが可能です。

書記モードの境界

CSSでは、同じ文書の中に異なる書記モード (たとえば horizontal-tb による横書きと vertical-rl による縦書き) を共存させることができます。このとき、異なる書記モードの境界点では通常のレイアウトアルゴリズムとは異なることが起きる可能性があります。

まず、親ボックスと異なる書記モードを持つボックスの内部表示型が flow の場合 (つまり、 display: inline または display: block の場合) は、 flowflow-root に強制的に読み替えられます。これは全ての書記モードの組み合わせで起きるため、たとえば同じ縦書きでも vertical-lr の中で vertical-rl が使われるようなことがあれば発生します。

さらに、親ボックスのブロック方向と子ボックスのブロック方向が互いに垂直である場合のことを、子ブロック側が直交フローを確立 (establish an orthogonal flow)していると呼びます。この場合、以下のようなルールが適用されます。

  • レイアウトアルゴリズムにはブロック方向とインライン方向を区別するものがあるため、このような場合にはどちらの方向を利用するのかを明確にする必要があります。このとき、
    • 子ボックスの大きさを計算するアルゴリズムは、子ボックスのフロー相対方向に基づいて実行されます。
    • 子ボックスの親ボックスに対する位置を計算するアルゴリズムは、親ボックスのフロー相対方向に基づいて実行されます。
  • 子ボックスのサイズ計算時の有効空間 (available space) がインライン方向に不定だった場合、このボックスがブロックコンテナーである場合に限り代替の制約が導入されます。たとえば横書きの文書はふつう縦スクロールするため縦に無制限に長くレイアウトされますが、その中に縦書きのブロックがあると、行を無限に長くできてしまいます。それによってテキストが読みにくくなるのを防ぐためにこのような制約があります。
  • さらに、子ボックスがブロックコンテナーである場合は常に複数列レイアウトが有効化されます。ただし、この規定はWriting Modes Level 4内でat-risk (安定的ではない仕様) としてマークされていることに注意が必要です。

Flexbox

フローレイアウトが文書のために作られたレイアウトであるのに対し、flexboxレイアウトは高度なGUIを備えたWebアプリなどを念頭に置いて作られたレイアウトです。このような要件では、文字の進行方向とは関係なく要素を好きな順番で配置したくなるため、flexboxは独自に主軸 (main axis)交差軸 (cross axis) という方向を定義しています。

これらの軸の役割はおおむねフロー相対方向に対応しています。主軸はflexboxにおけるインラインベース方向であり、交差軸はflexboxにおけるブロックフロー方向であるといえます。実際、デフォルトではこの対応関係が採用されます。

主軸と交差軸の方向は、flex-flowプロパティを使い、フロー相対方向からの相対的な方向として定義します。一行flexboxの場合、その自由度は4です。複数行flexboxの場合、自由度は最大の8であり、全ての直交変換が実現できます。

flex-flow 折り返しタイプ main-start cross-start
row 一行 inline-start block-start
row-reverse 一行 inline-end block-start
column 一行 block-start inline-start
column-reverse 一行 block-end inline-start
row wrap 複数行 inline-start block-start
row wrap-reverse 複数行 inline-start block-end
row-reverse wrap 複数行 inline-end block-start
row-reverse wrap-reverse 複数行 inline-end block-end
column wrap 複数行 block-start inline-start
column wrap-reverse 複数行 block-start inline-end
column-reverse wrap 複数行 block-end inline-start
column-reverse wrap-reverse 複数行 block-end inline-end

CSS Transforms

CSS Transforms 1は座標変換を可能にする仕様です。また、CSS Transforms 2は3D座標変換を可能にします。

これらの変換は物理座標に作用し、レイアウト上はあたかも変換がなかったかのように扱われます。たとえば以下の例では、外側のボックスの高さは変換がない場合と同じ値になっています。

<!doctype html>
<style>
  #box {
    font-size: 2em;
    width: 15em;

    border: 3px solid blue;
  }

  #inner {
    transform-origin: 0 0;
    transform: scale(0.8, 0.8);

    border: 1px solid red;
  }
</style>
<div id="box">
  <div id="inner">
    Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
  </div>
</div>

図: 上記のソースのスクリーンショット

ただし、レイアウト処理の中でも、オーバーフローの処理だけは変換の影響を受けます。

writing-mode: sideways-rl;writing-mode: sideways-lr; はテキストを90°回転するものであり、 transform: rotate(90deg);transform: rotate(-90deg); と似ています。しかし、以下のような違いがあります。

  • サイズの制約が、ボックスの外側と適切にやり取りされる。
  • 画像など、テキスト以外のコンテンツは回転されない。
  • 物理的な指定 (width / height など) は影響を受けない。

その他

傍線

text-decoration: underline; はボールド体やイタリック体と並んで一般的な文字装飾です。これには以下のような使用例があります。

  • <u> 要素[9]の意味論として定義されているもの
    • 中国語テキスト中の固有名詞
    • テキスト中にスペルミスがあることを表示するための記号
  • <u> 要素の不適な使用例として挙げられている (したがって、下線で表現する慣習が存在することが伺える) もの
    • 強調
    • テキスト中の重要な部分に印をつけてあとから参照するための記号
    • 本の題名
    • 術語、生物種等の学名、外国語の翻字、思索、船の名前

同様の装飾が縦書きテキストでも使われることがあり、傍線と呼ばれます。しかし、この傍線の位置に関する慣習は言語により異なるとされています。

  • 日本語や韓国語では一般的に、傍線をテキストの右側に記載します。
  • 中国語では一般的に、傍線をテキストの左側に記載します。

この慣習上の違いを反映するため、text-underline-positionプロパティが left / right 値を受け付けるようになっています。この効果は以下の通りです。

  • text-underline-positionleft / right 値を含まない場合:
    • underline / overline は行の方向に基づいて描画されます。つまり、underlineはunder側、overlineはover側に描画されます。
  • text-underline-positionleft / right 値を含む場合:
    • 縦書きタイポグラフィックモード (vertical-rl / vertical-lr) では、underline / overline は物理的な方向に基づいて描画されます。つまり、underlineは指定した側に描画され、overlineは指定したのとは反対側に描画されます。

脚注
  1. 個人的には証明木はしばしばこの順序で書かれると感じています。 ↩︎

  2. Bidiに対応していないWebブラウザの場合は、ここで説明したようには表示されない可能性もあります。 ↩︎

  3. これらの括弧のUnicode上の名称は U+0028 LEFT PARENTHESIS および U+0029 RIGHT PARENTHESIS となっていて、物理的な方向にもとづいて命名されてしまっています。しかし、実際の運用では論理的な順序に基づいて利用することになっています。 ↩︎

  4. ほとんどの括弧がこのリストに含まれますが、ある1対の括弧のみは互換性のためあえて鏡像化しないよう指定されているため注意が必要です。 ↩︎

  5. アラビア文字疑問符とは別に、ラテン文字テキスト中で皮肉を表すのに使える皮肉記号が U+2E2E REVERSED QUESTION MARK として定義されています。この2つは意味が異なるので注意しましょう。 ↩︎

  6. 紛らわしいことに、位取り記数法を利用する数字は文字よりも後に伝来したため、それぞれの地域で伝来元の名称で呼ばれています。つまり、ラテン文字圏で使われる数字はアラビア数字と呼ばれ、アラビア文字圏で使われる数字はインド数字と呼ばれます。 ↩︎

  7. ここではENの動作の説明のためにASCII % を使いましたが、アラビア文字用の ٪ という文字もあるようです。 ↩︎

  8. ここでの1文字とは、基本的には拡張書記素クラスターのことを指します。 ↩︎

  9. <b>, <i>, <u>, <s>, <small>, <sup>, <sub> はスタイル名をタグ名に冠していますが、現代のHTMLではそれぞれに適した意味論が割り当てられ、セマンティックなタグとして利用可能だと定義されています。 ↩︎

Discussion