第二回部会 - Part 2

概要:
CSS3 Writing Modesの研究 - Part 2
関連するW3C仕様:

BPS株式会社CSS部の第二回部会のPart 2です。 CSS 3のWriting Modesについてお送りしています。 Part 1では概要をお伝えし、Part 2から本編となります。

なお、この発表は2014年3月20日のCandidate Recommendationに沿って行われています。 書き起し時点で最新仕様を確認し、異なる部分があればその旨記載にするするように努めています。

ブロックの進行方向

writing-modeプロパティを指定すると、横書き・縦書きを指定すると同時に、ブロックの進行方向も決まります。 ブロックの進行方向というのは、ブロック要素が積まれたり、長い行が折り返したあと次に進む方向です。

横書きの文書では、文字は左から右に進むので正しいですが、行も左から右に進むなんてことはないですね。 もしあるとすると、下の図のように、横書きならやたら横に長い、1つのブロックになってしまいます。

momotaro-wide

このようなレイアウトがあるとしたら、writing-modeはさしあたりhorizontal-lr(書字方向は水平、ブロックの進行方向は左から右)のような名称になるでしょうが、実際にはありません。 普通、我々が目にするコンテンツでは、下の図ように、横書きでは縦にブロックが積まれます。

momotaro-wide

縦にブロックを積むのは上から下、下から上の2通りが考えられますが、概要で述べたように現用の文字体系の中には下から上に積む言語がありませんので、上から下に積むプロパティのみが用意されています。 それがhorizontal-tbです。tbはtop-to-bottomの略で、ブロックが上から下に進行することを示しています。

さて、縦書きではどうでしょう。

momotaro-tate

このようにブロックの進行方向も縦だと、一列になってしまいますね。 もうおわかりかと思いますが、当然このような指定は存在しないわけで、日本語の縦書きの場合は、下の図のように右から左にブロックが進行します。

momotaro-tate

このような指定をするプロパティ値はvertical-rlです。 rlはright-to-leftの略で、右から左ですね。

writing modesにはもうひとつ、vertical-lrという値があります。 これはモンゴル文字の縦書きで使われる値で、左から右にブロックが進行します。

mongolian

まとめると、writing-mode:に指定できるプロパティは下記の3つです。

  • horizontal-tb: 横書き。上から下にブロックが進行。
  • vertical-rl: 縦書き。右から左にブロックが進行。
  • vertical-lr: 縦書き。左から右にブロックが進行。

writing-modeの適用範囲

仕様では、このようになっています。

Name: writing-mode Applies to: All elements except table row groups, table column groups, table rows, and table columns

適用範囲: すべての要素、ただしテーブルの行グループ・列グループ・行・列を除く

writing-modeは、上記のようなテーブルの一部の要素以外、HTMLのすべての要素に適用可能です。 pタグのようなブロックレベル要素にも適用できますし、olに指定するとリストマーカーにも効果があります。

テーブルのグループ(対象外)

HTMLのテーブルで、すべての行の1列目だけに特定の書式を付けたいとき、<tr><td class="mycol1">1</td><td>2</td><td>3</td><tr>のように各行で毎回特定のセルを指定するのは非常に手間がかかります。 これを手軽にするために、colgroupの仕組みが用意されています。 colgroupを利用すると、下の図のように、縦の「…位」のセルをまとめて書式設定することができます。

d

colgroupというのは、テーブルの中で、カラムをまとめて書式設定するための仮想の制御構造です。

1
2
3
4
5
6
7
8
9
10
11
<table>
  <colgroup>
    <col span="1" class="orange_bg">
    <col span="2" class="yellow_bg">
  </colgroup>
  <tbody>
    <tr><th>1位</th><td>スイカ</td><td>☆☆☆☆☆</td></tr>
    <tr><th>2位</th><td>タコ</td><td>☆☆☆☆</td></tr>
    <tr><th>3位</th><td>福神漬</td><td>☆☆☆</td></tr>
  </tbody>
</table>

実質はセレクタのように機能していて、表示を持ちません。 このcolgroupのところを、突然縦書きとして行と列を入れ替えてしまうと、関係がぐちゃぐちゃになり、列の概念が壊れてしまいます。 例えば、下のようにcolgroupのスタイルにwriting-mode: vertical-rlを指定する、などです。

1
2
3
4
5
6
7
<table>
  <colgroup style="writing-mode: vertical-rl;">
    <col span="1" class="orange_bg">
    <col span="2" class="yellow_bg">
  </colgroup>
  <tbody>
    <tr><th>1位</th><td>スイカ</td><td>☆☆☆☆☆</td></tr>

もしこれが適用されるとすると、下の図のように、意図しない方向のセルが書式設定されてしまうでしょう。

c

そのため、colgroupについてはwriting-modeが適用外となっています。 ちなみに、HTML4.01では、width、align、valignなど書式を直接設定するプロパティがありましたが、HTML5では廃止されています。 書式指定はclassまたはstyleプロパティで行います。

行についても、thead、tbody、tfootの3つが行をまとめるタグとして用意されていますが、ここにもwriting-modeは適用外です。

テーブルの行・列(対象外)

テーブルの行および列に対しても、writing-modeは適用外です。 もし適用を許してしまうと、下のように、テーブルの一部だけでセルの進行が外れてしまうでしょう。

o

このような表は作らないということで、これもwriting-modeの指定対象外です。

テーブル

では、テーブル自体はどうでしょうか。 テーブル自体にはwriting-modeを指定でき、縦書きを指定すると、行は横方向、列は縦方向に走るようになります。 また、テーブルのセルも対象で、セル内のテキストはこのように縦書きになります。

b

colgroupについては心配ご無用です。 colgroupが指す意味範囲は変わらず、「…位」のまとまりを指し、テーブルの縦横が入れ替わりますが元々意図していたグループが崩れるといったことは起きません。 「…位」にまとめて色が付けられているのがわかります。

ページの進行方向

root要素のwriting modeは、ページの進行方向にも影響を与えます。 writing modesの仕様ではこのようになっています。

In paged media CSS2.1 classifies all pages as either left or right pages. The page progression direction, which determines whether the left or right page in a spread is first in the flow and whether the first page is by default a left or right page, depends on the writing direction as follows:

The page progression is right-to-left if the root element’s ‘writing-mode’ is ‘vertical-rl’ or if the root element’s ‘writing-mode’ is ‘horizontal-tb’ and its ‘direction’ is ‘rtl’.

The page progression is left-to-right if the root element’s ‘writing-mode’ is ‘vertical-lr’ or if the root element’s ‘writing-mode’ is ‘horizontal-tb’ and its ‘direction’ is ‘ltr’.

(Unless otherwise overridden, the first page of a document begins on the second half of a spread, e.g. on the right page in a left-to-right page progression.)

(CSS2.1のページ媒体では、すべてのページは右ページ・左ページに分類される。 ページの進行方向は、見開きにおいて右・左ページのどちらが先なのか決定し、先のページが右・左どちらがデフォルトなのか決める。 この決定は下記のように書字方向に依存する。

root要素のwriting-modeがvertical-rlのとき、またはroot要素のwriting-modeがhorizontal-tbかつdirectionがrtlのとき、ページ進行方向は右から左とする。

root要素のwriting-modeがvertical-lrのとき、またはroot要素のwriting-modeがhorizontal-tbかつdirectionがltrのとき、ページ進行方向は左から右とする。

オーバーライドがない限り、文書の最初のページは見開きの2ページ目から開始する。例えば、左から右への進行では、右ページが開始。)

英語の文書なら左から右、日本語の縦書きなら右から左、というようなページ進行になります。 ブラウザ上ではあまり意味がありませんが、これは印刷物を意図してページ制御を行う場合に、生きてきます。 ページ制御の定義はpaged mediaモジュールが担っていて、ページ番号や見出しを付けるといった機能について整備していくようです。

余談

BODYに文書全体のwriting-modeを指定すると、root要素のwriting-modeとして扱われるでしょうか。 いくつかのブラウザでは、そのような挙動になっているようですが、W3Cのメーリングリストでも議論が行われています。 公開メーリングリストでdirection propagation bodyなどで検索をしてみると、今年(2015年)に入っても議論がされていることがわかります。 EPUBの仕様では、メーリングリストのこのメールで触れられているように、writing-modeはbodyに指定するとなっているので、おそらくEPUB仕様をカバーするために伝播する仕様に落ち着くのではないかと思われます。

現在の仕様にはそうした案は入っておらず、BODYに指定されたdirectionは、root要素に伝播しない、となっています。

Note that the direction property of the HTML BODY element is not propagated to the viewport.

(HTML BODYに指定されたdirectionプロパティは、viewportに伝播しない。)

writing-modeも同様に、BODYからroot要素に伝播しないとなっています。

Note that the writing-mode property of the HTML BODY element is not propagated to the viewport.

(HTML BODYに指定されたwriting-modeプロパティは、viewportに伝播しない。)

余談

概要編でも触れましたが、CSS仕様のモジュール分けは、厳密な機能分離の単位ではなく、話題や議題のような大まかなくくりで行っています。 そのため、ある機能の定義を見たい場合に、複数のモジュールを読む必要があることが多いです。 また、この仕様を見ればよいな、と思ったところとは違うモジュールに、探していた記述がある、というのもよくあります。

まさに上の記述がその例で、ページ制御を扱うpaged mediaモジュールのほうで定義するとすっきりしますね。

ルビ

さて、仕様ではルビについて除外規定がありませんので、rbcやrtcにwriting-modeを指定したら、どうなるか興味があります。 ルビとルビが振られる漢字について、ルビの向きを無視して独立して方向を変える、といった操作が許されるのか、ということです。 下の図のBやCがその例です。

Writing mode on ruby

試しにMac版Chromeで、ruby base containerにwriting-modeを適用したみた結果です。 Bのように「せかい」を横書き指定すると、漢字の上に付くようです。 また、Cのように「世界」を横書き指定すると、「せかい」が漢字の左下に付くようです。 こうした表記は実際の使用例はないはずで、writing-modeの適用範囲からルビを除外してもよいものと思いますが、仕様の整合性がまだ取られていないのでしょう。

余談

ルビは、日本語の他に、中国語での併音表記や、台湾での注音符号に例が見られます。 これ以外の言語ではあまり例がなく、漢字文化圏特有の表記と言えます。

中国語の併音の例 併音

台湾の注音符号 注音符号

注音符号のルビは、上の図にあるように独特です。 この例文は横書きですが、ルビは漢字の上ではなく、漢字の右に入ります。 しかもルビ部分は縦書きです。 この仕様は、現在最新のCSS Rubyモジュール (2014年8月5日Working Draft)のにも盛り込まれて、ruby-position: inter-characterで指定できるとなっています。

特別ルール

writing-modeを指定すると、いくつか面白いことが起きるので、紹介します。

特別ルール1

  • インライン要素に、異なるwriting-modeを指定すると、inline-blockになる

インライン要素に、containing blockと異なるblock flow directionを持つwriting-modeを指定すると、このインライン要素はinline-blockになります。 具体的にはdisplay:のcomputed valueが変わり、display: inline-blockが指定されたのと同じ効果になります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style>
.tate {
  writing-mode: vertical-rl;
  -webkit-writing-mode: vertical-rl;
  color: blue;
}
.yoko {
  writing-mode: horizontal-tb;
  -webkit-writing-mode: horizontal-tb;
  color: red;
}
</style>
<body>
  <p>こんにちは、<span class="tate">縦書き</span>の世界!</p>
  <hr>
  <p>こんにちは、<span class="yoko">横書き</span>の世界!</p>
</body>

上のようなサンプルを作って、Chromeでプロパティを見てみます。 bodyはwriting-modeを指定していないので、全体としては横書きの文書になります。

tate

横書き中に入れた青字の縦書き部分をフォーカスすると、display: inline-blockになっているのがわかります。 このように、containing blockと異なるblock flowが入るときは、computed valueがこう変化します。

yoko

横書きの中で、同じ横書きのspanを入れるとどうでしょう。 block flowが変わらないので、computed valueも変わらず、display: inlineのままです。

なぜblock flowが変わるときにこのような処理が必要なのかというと、仮に、display: inlineのままでwriting-modeだけ横から縦に変更してしまうと、行の折り返し位置が未定義になってしまいます。 折り返しを定義できなくなり、レイアウトが決まらないため、それを避けるためにinline-blockになります。 この処理によって、コンテンツとして積み重ねられるのはblockおよびinline-blockとなるわけです。

特別ルール2

縦書きでは、文字以外の要素もwriting-modeに合わせて回転します。 例えば、セレクトボックスも回転します。

original

回転すると、下のようになります。 親要素にwriting-modeを指定することもできますし、セレクトボックス自体に指定することもできます。

vertical

余談

このサンプル画像はFirefoxで試したところで、アラビア文字がtopに寄せられています。 アラビア文字は、上のほうに寄せると文末方向に寄せることになりますが、これで良いでしょうか。

というのも、アラビア文字のコンテキストでは、リスト項目は右に寄せるのがデフォルトだからです。 縦書きと同時にセレクトボックスにdirection: rtlを指定すると、下のように、各項目がすべて下に寄せられます。

vertical-rtl

単一の項目だけを寄せたい場合は、<select>タグに対してtext-align: endなどが使えるようです。 なお、startやendの方向は、その項目が持っているテキストから見た方向ではなく、親(つまりセレクトボックス)のdirectionから見た方向です。 directionがlrtの場合、startは英字の行頭方向を指します。

ただ、現時点ではフォーム要素の書式設定には期待しないほうがいいかもしれません。 歴史的な理由で、仕様の遵守が緩いためです。 例えば、IEだとz-indexが効きません。また、Chromeではセレクトボックスは縦に回転せず、横向きのままです。 また、AndroidやiOSなどモバイル環境では、セレクトボックスが画面全体に覆い被さるUIになっていたりと、だいぶ異なります。

余談
  • セレクトボックスとコンボボックス

select-or-combo

ところで、このコントロールは、プルダウンがあるのでコンボボックスのように見えますが、どうでしょう。 …いいえ、セレクトボックスで合っています。 というのも、HTMLにはコンボボックスがないのです。

Mac OSの時刻設定にあるボックスのように、リストから選択もできるし、自分で入力もできる、というコントロールをコンボボックスと呼びます。

combo

確かに、HTMLの1行のセレクトボックスでは、中のテキストを書き換えられません。 書き換えられないということは、セレクトボックスの名称で良いのです。

ちなみに、サンプルの左のほうにある複数行のリストも、同じセレクトボックスと呼びます。 <select>タグで“multiple=true"を指定すると、複数選択できるボックスになります。

select

特別ルール3

  • replaced elementは回転しない

画像や動画です。 回転すると困りますね。 仕様ではこのようになっています。

The content of replaced elements do not rotate due to the writing mode: images, for example, remain upright. However replaced content involving text (such as MathML content or form elements) should match the replaced element’s writing mode and line orientation if the UA supports such a vertical writing mode for the replaced content.

(置換要素の中身は、writing-mode変更で回転しない。imageなどは正立のままとなる。 ただし、テキストを含むコンテンツ(MathMLやform要素など)は、置換要素のwriting-modeおよびline-orientationに従うべきとする (UAが置換要素についてそのような縦書きモード対応をしている場合))。

置換要素というのは、HTML5の仕様でここで定義されています。 画像や、canvasやvideoなどの埋め込みコンテンツなどとなっています。

MathMLのように、文字が含まれているコンテンツは、書字方向に合わせて回転してほしい、となっています。 例えば、日本語の縦書きの技術本で、数式が出た場合は、横書きの本のような横組みではなくて、時計回りに90度倒した組み方となります。

ブロックの進行方向 - orthogonal flow -

概要で話したブロックの進行方向の続きで、細かい話をします。

  • orthogonal のとき

orthogonalって何でしょう。 ウィズダム英和辞典によると、

orthogonal /ɔːrθɑ́(ː)ɡ(ə)n(ə)l|-θɔ́ɡ-/

〘数〙直角の, 直交の[する].

というように、数学用語で「直交」の意味です。 writing-modesのコンテキストで具体的にどのような事象を指すかというと、横書きの中に縦書きが入る、のような直交関係のことです。

  • 横書きの中に縦書きが入る。
  • 縦書きの中に横書きが入る。

といった直交関係になる場合の仕様が、CSS Writing Modesの7.3で定義されています。

When a box has a writing mode that is perpendicular to its containing block it is said to be in, or establish, an orthogonal flow.

(あるボックスのwriting-modeが、包含するブロックに対して直角を成すとき、「orthogonal flow状態にある」「orthogonal flowを形成している」と言う)

基本のボックスサイズ計算

直交する要素がある場合、ボックスサイズの計算が問題になります。 まずは、要素が横だけ・縦だけの基本の計算についてまとめます。

boxsize-basic

measure/extentは概説で出た用語ですね。

高さも幅も、autoが指定されていた場合は、各要素の大きさはどう決まるでしょうか。 横書きのとき、幅(measure)は画面いっぱい、高さ(extent)は文字がちょうど収まるサイズになります。 縦書きでも、同じように高さ(measure)は画面いっぱいに広がり、幅(extent)は文字がちょうど収まるサイズになります。

つまり、autoの指定は、measure方向については基本的に広がり、extent方向については子にフィットします。 ただし、inline-block、float、absoluteの一部のケースは例外です。

直交時のサイズ計算

writing-modeが直交している場合、どうなるでしょう。 autoのサイズ計算で「extent方向については子にフィット」としている部分が、循環依存してしまいます。

縦書きの子供を持つdivがあったとして、幅も高さもautoとなっているとき、幅はブラウザウインドウいっぱいになります。 高さは、子供にフィットします。 さてここで、子供の高さは、どうでしょう。 基本のサイズ計算に従うと、親いっぱいになるでしょう。 しかし親は、子が決まらないと、決まりません。 矛盾した定義になってしまいます。 autoではなくて100%でも同じです。

この場合、どう解決するのでしょうか。

親と子のどちらかを優先する、という案を出したとします。 しかし、子供を優先すると、高さは無限に広がります。 親を優先にすると、高さは0になります。 この案では解決できません。

子が決まるまで親が決まらない、ということは、親を一旦無視して子供を描画すると良さそうですね。 そのあとに親をかぶせていって、微調整するとか。 その通り、仕様では、initial containing blockのサイズを子に採用する、ということになっています。

Putting a box in an orthogonal flow allows the opposite to happen: for the available extent to be defined, but the available measure to be indefinite. In such cases a percentage of the containing block measure cannot be defined, and inline-axis computations cannot be resolved. In these cases, the initial containing block’s size is used as a fallback variable in place of the available measure for calculations that require a definite available measure.

(ボックスを直交に配置すると、逆のことが起きる。 すなわち、extentは決まるが、measureが無限になる。 この場合、包含するブロックのmeasureが定まらず、inline要素の座標計算が解決しない。 そうしたときはinitial containing blockのサイズをフォールバックとして、計算用のmeasureの確定値として代替する。)

initial containing blockは、viewportのサイズです。 要するに、ブラウザのウインドウサイズです。 paged-mediaでページとして切り出される場合は、pageのサイズです。

下の図の、縦書き部分についてのmeasureが、ブラウザのウインドウサイズになります。 これが決まることによって、親のextentは無事に子のサイズを参照することができ、子がちょうど収まるサイズに解決することができます。

icb1

少しスクロールしてみましょう。ブラウザウインドウの高さが、縦書き部分のmeasureと一致していることがわかります。

icb2

こうして、子のautoや100%には、ブラウザのウインドウサイズが代替として入ります。 どんなに長いページだとしても、ウインドウサイズが代替となるのは変わりません。

ただし、子要素の文章が短く、measureがブラウザウィンドウサイズに到達しない場合は、ウインドウサイズは使われません。 子要素は短いのですから、そこまでの長さが要素のサイズとして使われます。

Writing Modes仕様の7.3.2で、このことが説明されています。

If column-count and column-width are both auto, calculate the used column-width as: min(max-content, max(min-content, min(fill-available, fill-fallback))), where:

min-content: the min-content measure of the box

max-content: the max-content measure of the box

fill-available: the fill-available fit into the box’s containing block’s size in the box’s inline axis

fill-fallback: the fill-available fit into the initial containing block’s size in the box’s inline axis

(column-countとcolumn-widthが両方ともautoの場合、column-wideの計算は、 min(max-content, max(min-content, min(fill-available, fill-fallback)))の式により行われる。

min-content: ボックスの最小のmeasure

max-content: ボックスの最大のmeasure

fill-available: インラインの進行方向のinitial containinng blockのサイズに合わせる

fill-fallback: インラインの進行方向のinitial containing blockのサイズに合わせる)

子要素の文章が短い場合は、上の式のmin-contentが一番小さくなるでしょう。 式に当てはめてmin-contentが使われるというわけです。

余談

ところで、html要素にpaddingが入っていた場合は、どうなるでしょう。 htmlにpaddingを入れても、フォールバック先であるinitial containing blockつまりviewportのサイズが変わるわけでないので、計算結果には影響しないはずです。 ただし、これを正しく実装しているブラウザは、あまりないようです。 Safariではhtmlのpaddingを指定すると、それに影響され、このようにmeasureがpaddingの分、少なくなります。

icb3

ここまで、ブロックの進行方向のお話しでした。 次回、行の向き、文字の向き、縦中横などをお送りします。