<canvas> の 2D コンテキスト)実装: arm/v1/src/os_lib/canvas2d.rs(純粋モジュール)
試験: arm/v1/tests/src/test_canvas2d.rs(40 件)
getContext("2d")
は既に実装されており、fillRect / arc /
bezierCurveTo / drawImage /
getImageData など 30 以上のメソッドが
登録済みだった。ただしすべて
dom_noop(何もしない)へ接続されており、
canvas を使うページでは一切描画されなかった。
状態プロパティ(fillStyle/lineWidth/font
等)も保持されるだけだった。
つまり「API 表面はあるが中身が無い」状態。
ハードウェア(画面)に依存しない部分をすべて扱う。
Matrix):
合成・適用・行列式・逆行列PathBuilder):
moveTo/lineTo/closePath/
ベジェ(2 次は 3 次へ昇格)/円弧/rectscanline_spans):
nonzero / evenoddSurface):
ピクセル合成・矩形塗り・ パス塗り・線描Canvas2dContext /
DrawState):
変換の積み上げ、save/restore、globalAlpha、
fillRect/clearRect/fill/stroke
の実行create_context/with_context):
JS オブジェクトと Rust 側の状態を id で結び付けるJS 側のコンテキストオブジェクトは _c2d_id(registry の
id)だけを持ち、 実体は Rust 側の表にある。JS の値へ Rust
の可変状態を直接埋めると借用と 寿命の扱いが複雑になるため、既存の DOM
プロキシ(style/classList)と同じ 「id で引く」流儀に合わせた。
接続済みのメソッド: fillRect / strokeRect /
clearRect / beginPath / closePath
/ moveTo / lineTo / rect /
arc / bezierCurveTo /
quadraticCurveTo / ellipse /
roundRect / fill / stroke /
save / restore / translate /
scale / rotate / transform /
setTransform / resetTransform。
roundRect の半径は数値 1 個でも配列(1〜4
要素)でも取れる。 補完規則は CSS の border-radius
と同じ(1 個なら全隅、2 個なら対角の組、 3
個なら左上・対角・右下)。半径の和が辺長を超える場合は比率を保ったまま
縮小する——しないと角が重なって図形が破綻する。
restore() は変換だけでなく
globalAlpha/lineWidth も JS 側へ
書き戻す。これらは JS の素のプロパティなので、Rust 側で戻すだけでは JS
から読んだときに古い値が見えてしまう。
色(fillStyle/strokeStyle)は文字列表現を保持していないため
戻せない(既知の割り切り)。
fillStyle 等は JS
の素のプロパティで代入を検知できないため、
各描画メソッドの入口で毎回読み直す(canvas_sync_state)。
色は CSS の parse_color をそのまま使う。
同時に保持するコンテキストは 8 個までで、超えたら古い id から捨てる。 ページ遷移のたびに面(最大 64MB)が積み上がるのを防ぐため。
曲線分割規則と巻き数計算という目視では気づけないバグ源を試験で固定できる。
<canvas>
は「レイアウトで決まった矩形へ、自分のピクセルを貼る」要素。
画面座標へ直接描くと、スクロール・クリップ・再描画のたびに canvas
の内容を作り直す必要が出る。Surface(ARGB8888 配列)へ
キャンバス内座標で完結させておけば、貼り付けだけが画面側の仕事になる。
面のサイズは 4096×4096 を上限とし、超える指定は None
を返す (ヒープを食い潰さないため)。
transform()
は現在の行列へ右から掛ける(self * m)。
後から積んだ変換ほど点に対して先に効く。取り違えると
translate してから rotate
した図形が画面原点回りに回る。invert()
は None。 ゼロ除算で NaN を伝播させない。moveTo
のみ)は捨てる。 残すと 0 長の線を描こうとする。moveTo 無しの lineTo
は、その点への moveTo と同じ扱い。[ylo, yhi)。
閉区間にすると、頂点をちょうど走査線が通るときに上下 2 本の辺が
同じ交点を二重計上し、evenodd で内外が反転する
(塗り残し・塗り過ぎの典型的な原因)。scanline_spans
の返す区間は昇順・非重複。nonzero(既定): 巻き数が 0 でなければ内側。
同じ向きに巻いた入れ子では穴が開かない。逆向きなら開く。evenodd:
交差回数が奇数なら内側。入れ子は常に穴が開く。両者の違いは入れ子図形でしか現れないため、試験で明示的に区別している。
Surface)clearRect
は合成せず上書きする(replace=true)。
合成にすると不透明な既存色が消えない。y + 0.5)で判定する。
境界ちょうどで判定すると、隣り合う図形の継ぎ目に隙間や二重塗りが出る。save() の入れ子には上限(32
段)を設ける。 restore
を呼ばないスクリプトでヒープを食い潰さない。restore()
の呼び過ぎはエラーではない(仕様どおり無視)。fillRect
は矩形の高速路を持たず常にパス経由で塗る。
軸平行かどうかで分岐すると、回転が入ったときに誤った矩形を塗る。clearRect は
globalAlpha の影響を受けない(仕様どおり)。
回転時は外接矩形を消す近似(clearRect
に回転を掛ける使い方は稀)。globalAlpha
は色のアルファへ掛け算で合成する。
半透明色はさらに薄くなる。roundRect
の半径は、和が辺長を超えたら比率を保って縮小する。
角が重なって図形が破綻するのを防ぐ。miter は外側 2
辺の交点まで尖らせるが、 鋭角では尖りが際限なく伸びるため
miterLimit(既定 10)を 超えたら bevel
へ落とす。交点が求まらない(平行)ときも同様。lineCap/lineJoin
に未知の値を入れた代入は捨てる(仕様)。
既定値へ戻すのではない。image_results_ready
の注記を参照)。clearRect
もクリップに従う。合成を飛ばすため blend_pixel
を通らず、取りこぼしやすい。clip()
は領域を狭めるだけで広げられない。putImageData
は合成せず上書きし、クリップも無視する。
fill 系とは意味論が違う。createImageData
は透明で埋めた新しい配列を返す。
面の内容は読まない(getImageData と混同しない)。getImageData
で面の外に出た部分は透明で埋める。
切り詰めると返る配列の大きさが要求と食い違う。canvas.width/height
への代入は、同じ値でも
内容と描画状態をまるごと初期化する。
canvas.width = canvas.width
という消去の定型句がこれに依存する。drawImage の引数は 3・5・9
個で意味が変わる。 9 個のときだけ転送元の切り出しが指定される。Arc
で共有する。 複製すると数 MB
の写真を二重に持つことになる。draw_image
へ渡す)。別実装にすると変換への追従を二重に持つことになる。GLOBAL_VECTOR_FONT
は毎フレームの
文字描画が使うため、握ったままだと優先度逆転を起こす。measureText
は字形の送り幅を実測する。
バイト長からの概算は日本語で大きくずれ、文字サイズも反映されない。font
の語順は自由なので、px で終わる数値を探す。
pt/em は換算しない(見つからなければ既定の
10px)。レイアウトは StyledNode しか見ないため、JS
のオブジェクトを直接引かせず DOM 属性 _c2d_id
を橋渡しに使う。
getContext("2d") が registry へコンテキストを作り、id
を DOM 要素の _c2d_id 属性へ書く<canvas> 要素からその属性を読み、
RenderElement::canvas_ctx_id へ載せるSurface
を引き、要素の矩形へ転送する面のサイズと要素のサイズが違う場合は最近傍で拡縮する。 CSS で
width/height を指定した canvas
は、属性の解像度と表示サイズが
食い違うのが普通なので、この経路は必須。
lineWidth は stroke /
strokeRect に反映される。各線分を
「線幅ぶんの太さを持つ四角形」へ展開して塗り、結合部には円を置く。 ##
線端・線結合
lineCap は
butt(既定)/round/square、lineJoin
は miter(既定)/round/bevel
に対応する。miterLimit も反映する。
低水準の stroke_path_width は端 butt・結合
round 固定で、 stroke_path_styled
へ委譲する。既存の描画の見た目を変えないための
互換窓口であり、新しい呼び出しは stroke_path_styled
を使う。
canvas
はバッキングストア(Surface)へ描くだけなので、そのままでは
画面に出ない。画素を書く入口(fill_rect/fill_path/stroke_path/
stroke_path_styled)で旗を立て、ブラウザの再描画判定がそれを見る。
旗は描画の開始時に降ろす(take_canvas_dirty)。
setLineDash/lineDashOffset は
stroke/strokeRect に反映される。 クリップは
DrawState が Arc<ClipMask>
として持ち、描画の直前に Surface
へ写す。save/restore
は状態を複製するだけなので、
クリップは正しく戻る(色と違って文字列表現に依存しない)。
getImageData/putImageData
は面の画素を読み書きする (createImageData
は透明のまま)。canvas.width/height への代入は
面を作り直して描画状態を初期化する。
drawImage は <img>(src
で共有の置き場を引く)と別の canvas (_c2d_id
からコンテキストを引く)を転送元にできる。
変換・globalAlpha・クリップに従う。
fillText/measureText
はベクトルフォントの字形を使う。
textAlign/textBaseline/font
の大きさに従う。 strokeText
は塗りで代用する(線でなぞる形は未対応)。
ベースラインの比率はこの処理系の字形配置に合わせた近似で、
フォントごとの実測値ではない。
restore()
で色を戻せない(文字列表現を保持していないため)strokeText の輪郭描き(現状は塗りで代用)Path2D(clip(path, rule)
の形は未対応)imageSmoothingEnabled
は未対応)stroke)の線幅・線端・線結合(現状は線幅 1
固定)clip())<canvas width height> の動的変更への追従(現状は
getContext 時のみ)