Shiki’s Weblog

ESウェブブラウザ通信 - CSS 2.1 Test Suite #10

2011/12/17 #ESウェブブラウザ通信

気がつけばCSSテストスイートのブログも今回で10回目になりました。前回はHTMLテーブルの実装を進めました。今回はフォントまわりの改善や、これまでのスキップしてきていた9章のフロートに関するより複雑なテストを進めていきます。

フォント

これまではTrueTypeフォントから32pxのフォントをFree Type 2で展開してOpenGLで拡大・縮小して表示していたのですが、特に小さいテキストでの読み難さが気になってきました。

r2184r2184

r2185では、11px, 22px, 44pxと3つ異なる大きさのフォントをFree Type 2で展開して、OpenGLのミップマップを使うように変更しています。(ピクセル単位の部分ですので細かい違いはスクリーンショットをクリックして拡大して見てみてください。)

r2185r2185

ミップマップを使う関係上、TrueTypeのヒンティングは使わないようになっているのでコントラストは低くなってしまっていますが、ひとまずはこのスタイルで進めていきます。補足 : ヒンティングを使っていると以下のような感じになって、フィルター(GL_LINEAR_MIPMAP_LINEAR)が意図通りに働きません。"世界"の部分などを見てみると、サイズの異なるフォントテクスチャがきちんと重なり合わないことが分かるかと思います。

ヒンティングとミップマップの不整合の例

また、フォントのテクスチャマッピングに関する基本的な議論については1997年頃のページのようですが以下のページが参考になります。

A Simple OpenGL-based API for Texture Mapped TextMark Kilgard

著者のMark KilgardさんはESウェブブラウザでも使用しているGLUT(OpenGL Utility Toolkit)の作者でもあったのでした。

ズーム

ミップマップを利用することで比較的スムーズにフォントを拡大縮小できるようになってきたので、r2187ではマウスのホイールを使って10%きざみでズームイン、ズームアウトできるようにしてみました。以下はAcid1のページで倍率を変えているときの様子です。

ズームインズームイン

ズームアウトズームアウト

スマートフォンのブラウザではごくふつうの処理になりましたが、デスクトップでも悪くないかな、と思ったりしています。まだズームインしたときのパンを実装していませんが、おいおい作り込んでいくことにします。

@mediaルール

表示できるサイトを増やしていこう、ということで今回はウィキペデイアに挑戦です。ウィキペデイアは@mediaルールを使って表示デバイスに合わせたレイアウトをしているのですが、@mediaルールにESウェブブラウザが対応していなかったのでした。

r2191r2191

r2191で@mediaルールに対応しました。ところどころベースラインが揃っていない箇所が残っています。

r2198r2198

r2198でセルのvertical-alignとインライン要素のvertical-alignを区別していなかったバグを修正しています。(前回のr2181の修正が必要だった直接の原因は実はこちらのバグの所為だったりします。修正自体はいずれにしても必要なものでしたが。)'」'の前で改行してしまっているというテキストまわりのバグが見える以外は結構よくなってきました。

フローティング ボックスとマージン

ふたたびCSSテストスイートに戻ります。まず、8.3.1のマージンのつぶしの実装が完了していないとテストするできなかった、マージンの処理と連動している9.5節のフロートのテストを進めていきます。・floats-015 このテストは#4で一度対応したテストですが、マージンのつぶしの処理を入れた後に(違う形で)また崩れるようになっていました。

r2198r2198

マージンのつぶしの実装では、collapse-throughに対応するために、上マージンはすぐには確定しないで、FormattingContextにマージンを確定するまで保持しておく実装にしました。ただ、このテストでは右下の青いフローティングボックスは上マージン10px分を先に消費しておかないと綺麗に横一列に並べることができません。マイナスのマージンのことを考えるとどう実装したらよいか頭痛がしてきそうなところですが、CSSの仕様書はよくできていて、

But in CSS 2.1, if, within the block formatting context, there is an in-flow negative vertical margin such that the float's position is above the position it would be at were all such negative margins set to zero, the position of the float is undefined.    -9.5.1

と、そういう場合は未定義、ということにしてくれています。

r2199r2199

r2199では、マージンのつぶし関連の処理とは別個に、フローティングボックスをレイアウトする前にその時点のマージンの高さ分だけレイアウト済みのフローティングボックスの高さを消費してしまうようにしました。・clear-float-003 このテストはこれまでのところ対応に一番苦労したテストかもしれません。HTMLファイル中のコメントには、

Clearance is introduced as spacing above the top margin after margin collapsing occurs.

と書かれているのですが、緑色のフローティングボックスのclear: rightの処理を最初に行うときには、直前のcollapse-throughしたボックスのマージン96pxが赤いフローティング ボックスの高さをすべて消費しているのでクリアランスが発生しません。ただし、そのままマージンのつぶし処理をかけると、このギャップを作っていた96pxのマージンが親の上マージンに吸収されてしまって下図のような結果になってしまいます。

r2201r2201

IEをはじめメージャーなブラウザでは、コメントに書かれている通り、このような状態にしたあとで改めて96px分のクリアランスを導入して、緑色のボックスを下に96px移動させるような実装をしているように見えます。ただ、そういう動作は直感的には浮かんでこないと思うのですが、どうでしょうか? マージンのつぶし処理はクリアランスの有無で動作が変わる(clearance is introduced, and margins collapse according to the rules in 8.3.1. -9.5.2)のに、マージンをつぶしてからクリアランスを導入するというのはすこし違和感があったので、r2201以降さらに修正を加えていって、最終的に落ち着いたのがr2205です。

r2205

r2205では96px分のマージンが親に吸収されないような実装になっています。レンダーツリーは以下のようになっています:

* block-level box [html] (0, 0) w:816 h:253.051 m:0:0:0:0 transparent
   * block-level box [body] (0, 0) w:800 h:227.131 m:17.92:8:8:8 transparent
     * block-level box [p] (8, 17.92) w:800 h:17.2109 m:0:0:0:0 transparent
       * line box (8, 17.92) w:361.278 h:17.2109 m:0:0:0:0
         * inline-level box (8, 17.92) w:361.278 h:17.2109 m:0:0:0:0 "Test passes if there is no red visible on the page." #000000
     * block-level box [div] (8, 35.1309) w:192 h:192 m:17.92:608:0:0 transparent
       * block-level box [anonymous] (8, 53.0509) w:192 h:0 t:0 m:0:0:0:0 transparent
         * line box (8, 53.0509) w:0 h:0) m:0:0:0:0
           * block-level box [div] (104, 53.0509) w:96 h:96 m:0:0:0:0 #ff0000
       * block-level box [div] (8, 53.0509) w:192 h:0 t:0 m:0:0:0:0 transparent
       * block-level box [anonymous] (8, 53.0509) w:192 h:0 t:0 m:96:0:0:0 transparent
         * line box (8, 149.051) w:0 h:0 m:0:0:0:0
           * block-level box [div] (104, 149.051) w:96 h:96 m:-96:0:0:0 #008000

最後の行の緑色のフローティングボックスを保持している匿名ボックス(*)に、上マージン96pxが残って、値0のクリアランスがあるときのようなレイアウトになっています。

(*)9.5.1の"When the float occurs between two collapsing margins, the float is positioned as if it had an otherwise emptyanonymous block parenttaking part in the flow."に対応する匿名ボックスです。

補足: このテストの期間中だけ、Fedora 16の動作が不安定な時期に被ってしまって、スクリーンショットがKDEのものからGNOMEのものに変わっています。・floats-104.htm,floats-105.htm これは左右のマージンなのでマージンのつぶしは関係ないのですが、やはりマージンがらみということでTODOになっていたテストです。

r2205 - floats-104r2205 - floats-104

r2205 - floats-105r2205 - floats-105

104, 105ともに フューシャ のフローティングボックスが紫色の左マージンのあるボックスの中で生成されているのですが、r2205では、それ以降の緑色のボックスの中では、その左マージンがなかったかのようにテキストをレイアウトしてしまっています。

r2206 - floats-104r2206 - floats-104

r2206 - flosts-105r2206 - flosts-105

r2206で、左右のフローティングボックスのエッジをフォーマッティングコンテキスト内の絶対座標で保持するように修正して、こういった場合にも対応できるようにしました。・floats-138 このテストではクリアランスによってLine 2がLine 1よりも下にいけばOKです。

r2206r2206

フロートのテストに含まれていますが、r2206はフロートの処理には問題はなくて、マージンのつぶし処理に関するバグが残っていたのでr2207で修正しています。

r2207r2207

マージンのつぶしについては、Mozillaの開発をされているDavid Baronさんは昨年のブログの中で、collapse-throughには実際のユースケースはおよそないにも関わらず仕様の詳細化に集中しすぎてしまったと述べられています。仕様を策定するときには、実装を複雑にし過ぎないように立ち止まって仕様を見直すことも大切だったと。

if you're fixing a spec that's unclear, you should evaluate whether the complexity of the fix is really needed - David Baron

およそ誰も使わなさそうな場面の動作の仕様は詳細に動作を詰めるよりも、先ほどのマイナスのマージンの場合の様に未定義としておいたり、上限値を決めたりしてしまった方がいい、というのはブラウザの仕様策定に限らない話かもしれないですね。

より複雑なフロートのテスト

c414-flt-fit-001

r2207r2207

r2207では2つのバグが複合して崩れていました。1つは、最後の右下のimgはフロートではないので、レイアウト中にラインボックスをシフトダウンすることになるのですが、シフトダウンしてできたスペースに未処理のフローティングボックスが収まる場合は、先にそれを収めないといけません。r2207では、左下のイメージよりも右下のイメージが先に配置されてしまっています。もう1つのバグは、シフトダウンするときには、もっとも内側の残りの高さの低いフローティングボックスから処理していくわけですが、このテストの場合は左上と右上のフロートで配置されているイメージは、高さが同じなので1回のシフトダウンでをどちらも消去されます。r2207ではラインボックスの利用可能な幅がそれに対応して広げられていなかったので、右下のimgの前に空白が残っています。

r2208r2208

r2208でこれらのバグを修正しました。・c414-flt-fit-005 このテストでは上下のパターンが同じになるだけではなくて、赤い部分が見えてもいけないのですが、r2208ではフローティングボックスの位置はあっているものの、赤い部分が見えてしまっています。

r2208r2208

これはESウェブブラウザの初期の実装の誤りで、フローティング ボックスの高さを包含ブロックの高さに含めてしまっていたバグのたぶん最後のものです。フローティング ボックスをシフトダウンしながら配置していくときにラインボックスのマージンを使ってしまっていたので、下側のテストパターンでブロックボックスに高さが発生して赤い背景が描画されてしまっていました。

r2209r2209

r2209でLineBoxクラスのclearanceメンバ変数(CSSの仕様書で言うclearanceとは関係ありません)にシフトダウンする高さを保持するように修正して、ブロックボックスには高さが発生しないようにしました。・c5525-fltmult-000 このテストはテーブル関連でスキップしていたテストです。

r2209r2209

フロートの処理には問題はなくて、下側のテーブルでつくったリファレンス パターンでHTMLのcellspacing属性を使っているのですが未対応でした。

r2210r2210

r2210で対応しています。なお、HTMLのcellspacing属性はHTML 5ではnon-conformingにされているので、これからウェブページをつくる時はCSSのborderspacingプロパティを使いましょう、というお話でした。・floats-placement-vertical-004 このテストは、文字が小さくて見にくいのですが、緑色のフローティングボックスの横に'H'が1文字あって、改行したあと本当ならすぐその真下から(緑色のボックスの右側に)青色のフローティングボックスが配置されないといけません。

r2210r2210

ブラウザの内部処理的には、通常のインライン要素のレイアウト部分から、収まりきらなかったフローティングボックスの処理に切り替わる部分で、実はシフトダウンしなくてもフローティングボックスが収まってしまう場合があったのでした。よい境界条件のテストになってます。

r2211r2211

r2211でバグを修正しています。・floats-rule7-outside-left-001 これはDavid Baronさんの書かれたテストみたいですね。このテストは現状すべてのメジャーなブラウザで崩れているそうなので、CSS 2.1のテストスイートのページのfloats-rule7-outside-left-001の横にある'='マークをクリックするとリファレンスのレンダリングを見ることができます。(Mozillaから提供されているテストはこういうパターンで、別の方法でレイアウトした画面と同じになるかどうかテストさせるものがちょこちょこあります。)

r2212r2212

r2212でも他のブラウザと同じように崩れてしまっています。「これで良くない?」ということがW3Cのメーリングリストでも議論されていたようですが、現在の仕様書通りに実装するとやはりリファレンス通りになると思います。Firebugなどで見てみると、青色のフローティングボックスは包含ブロックの右端を突き抜けてしまっていることがわかると思います。

r2213r2213

r2213で修正しています。ただGeckoまでなぜ未対応なんだろう、ということで調べてみたところ、こちらにDavid Baronさんからバグがファイルされているようです:

https://bugzilla.mozilla.org/show_bug.cgi?id=616334

今日(12/17)の時点でも、まだ"Assigned To: Nobody; OK to take it and work on it"だそうです。・floats-bfc-001 フロールートはフローティングボックスと重なり合ってはいけない、という仕様のテストです。

r2213r2213

CSS 2.1の仕様書ではこのあたりの規程は緩めで、フロールートのところで必要ならクリアすべき(should)だけれど、クリアしないでも配置できるスペースがあったらそこに配置してもいい(may)よ、という感じになっているようです。

If necessary, implementations should clear the said element by placing it below any preceding floats, but may place it adjacent to such floats if there is sufficient space.  -9.5

r2214r2214

r2214では、ひとまずフロールートのところでクリアという実装にしています。なお、テストスイートの次のfloats-bfc-002margin-collapse-clear-010は、クリアしないで空きスペースに配置する実装を仮定しているようです。仕様書ではそうしないといけない、というようには読めないのですが、Optionalにはされていないようです。今回はこの2つの修正はスキップしておきます(現状のESウェブブラウザの実装だとすこし大きめの変更になるので)。

リグレッション テスト

ここまでで色々と変更が入ってきたので、また9.5節以外のテストで崩れたものがないかどうか確認しておきます。・margin-bottom-103 collase-throughを使っているテストなのですが、重なり合うはずの赤と黒のボーダーがずれてしまっています。

r2214r2214

これは、clear-float-003の一連の修正でclear-001も同時にパスするように行っていた修正が間違っていたというものでした。CSSの仕様書には、

The top margin of an in-flow block element collapses with its first in-flow block-level child's top margin if the element has no top border, no top padding, and the child has no clearance. -8.3.1

という例が載っていますが、ここは実際にはもう少し複雑ですね(8.3.1の the above rules imply that: に続く部分は例なのでこの通り実装したらいいというわけではない点は注意なのです)。

r2215r2215

r2215でcollapse-throughしている先頭の子の直後に、クリアランスのあるボックスが続いている場合は、先頭の子自体にはクリアランスがなくても、上ボーダーエッジをクリアしないという処理に修正しています。これで、clear-001margin-bottom-103も同時にパスできるようになりました。クリアランスがある場合、その上下のボックスは一塊になっているように処理しないとおかしくなってしまうのでした。・c5526c-display-000 ふたたびAcid1の登場です。r2515では再び崩れてしまっています。

r2215r2215

このテストでは、HTMLソース中のul要素は、画面上で右上の黒枠の中の左上から順番に4つ目のpluot?までのフローティングボックスだけを含んだブロックボックスになっています。r2208まではこのブロックボックスは1行目のフローティングボックスの高さを持ってしまうというバグがあったので、結果的にそれまでAcid1にパスしているように見えていたのでした。r2209の修正によって、このフローティングボックスしか含んでいないul要素のボックスの高さは正しく0となっているので、逆に続く5番目、6番目のフローティング ボックスの位置が上にズレてしまっているのでした。このようにフローティング ボックスの高さを一部でも消費している、collapse-throughしたボックスに続くボックスは、その消費された分だけはじめからクリアランスがあるかのように中のボックスを配置していかないといけないことがわかります。

r2216r2216

r2216で、collapse-throughしたボックスが消費したフローティングボックスの高さをフォーマッティング コンテキストに保存しておくようにして対策をしています。9.5のフロートのテストでは、まだモジラから提供されているfloats-wrap-*シリーズがほぼ全滅していまず。ラインボックスの高さの途中で次のフローティング ボックスのクリアランスが確保できた場合の処理など、Geckoでよく実装されている部分も、このシリーズでテストされています。ESウェブブラウザでは、今先にここに手をつけてしまうと、後々のテキスト処理の修正が大変なことになってしまいそうなので、テキスト処理の実装を落ち着いてから改めてテストを進めていくことにします。

まとめ

今回の最後のr2216では、CSS 2.1のW3Cのテストスイートの成功率が約48%というところまで来ました。今後の予定としては、まだ手をつけていないリストの実装などを進めてから、セレクタまわりの処理のテストと高速化(今の実装はものすごく遅いのです)を進めいって、単体のブラウザとしての使い勝手を向上させていくような流れで考えています。