Skip to main content

atmos/os_lib/web_engine/
paint_order.rs

1//! 描画順(スタッキング順)の算出。
2//!
3//! ## 背景 — 実際に踏んだバグ
4//! 描画側は全要素を `z_index` でフラットにソートしていた。ところが実サイトの CSS には
5//! ```css
6//! section { position: relative; z-index: 5 }
7//! ```
8//! があり、`<section class="hero" id="home">` は z=5、その子孫
9//! (`.hero-overlay` / `.hero-content` / `.hero-title`)は `position: static` なので z=0 だった。
10//! フラットソートでは **セクションが自分の子孫より後に描かれ、自分の背景で
11//! 自分の中身を丸ごと消してしまう**。実測でも、ヒーローテキストは描画直後に
12//! 1655px 残っているのに `draw()` 終了時点で 0 になっていた。
13//!
14//! ## 仕様(CSS 2.1 Appendix E)
15//! スタッキングコンテキストを作る要素は、**自分の背景を先に**描き、子孫はその
16//! コンテキストの中で描かれる。**子孫が祖先の背景より前に描かれることは無い**。
17//!
18//! ## 実装方針
19//! 各要素の「実効 z」を `max(自分の z, 祖先の実効 z)` とする。
20//! 文書順では祖先が必ず子孫より先に現れるため、実効 z が同値なら安定ソートが
21//! 祖先→子孫の順を保つ。これで祖先の背景が子孫を塗り潰す事故は起きない。
22//! 子孫が自前でより大きい `z-index` を持つ場合はその値が勝ち、祖先より後=
23//! 手前に描かれる(これも仕様どおり)。
24//!
25//! グローバル状態にもハードウェアにも依存しない純粋ロジックのみで構成する。
26
27extern crate alloc;
28
29/// 実効 z の算出でおかしな入力を検出した場合のエラー。
30#[derive(Debug, PartialEq, Eq, Clone, Copy)]
31pub enum PaintOrderError {
32    /// 祖先チェーンの深さが上限を超えた(循環参照や壊れたツリーの疑い)。
33    DepthLimitExceeded(usize),
34}
35
36/// 祖先チェーンの深さ上限。これを超えるツリーは異常とみなす。
37pub const MAX_DEPTH: usize = 512;
38
39/// 要素の実効 z を求める。
40///
41/// - `own_z` … その要素自身の `z-index`(`position: static` なら 0)。
42/// - `ancestor_effective_z` … 親要素の**実効 z**(ルートなら `None`)。
43///
44/// 祖先より小さい z は祖先の値まで引き上げる。これにより
45/// 「子孫が祖先の背景より先に描かれる」ことが構造的に起こらなくなる。
46pub fn effective_z(own_z: i32, ancestor_effective_z: Option<i32>) -> i32 {
47    match ancestor_effective_z {
48        Some(a) if a > own_z => a,
49        _ => own_z,
50    }
51}
52
53/// 描画ソートキー。`(実効 z, ツリー深さ)` の昇順で並べる。
54///
55/// **深さを第 2 キーにするのが要点**。要素列(`elements`)は祖先が子孫より
56/// 先に積まれるとは限らず、実効 z を揃えただけでは安定ソートが誤った
57/// emit 順をそのまま保存してしまう(実際にこれで回帰した)。
58/// 深さで比べれば、同一 z 内では必ず浅い要素=祖先が先になり、
59/// 不変条件 I-1 が emit 順と無関係に構造的に保証される。
60pub fn paint_sort_key(effective_z: i32, depth: u32) -> (i32, u32) {
61    (effective_z, depth)
62}
63
64/// 祖先チェーン(ルート→親の順)から実効 z を求める。
65///
66/// 深さが `MAX_DEPTH` を超えた場合は黙って打ち切らず、
67/// エラーログを出して `Err` を返す。呼び出し側は安全側
68/// (その要素の own_z をそのまま使う)へフォールバックすること。
69pub fn effective_z_from_chain(own_z: i32, ancestor_zs: &[i32]) -> Result<i32, PaintOrderError> {
70    if ancestor_zs.len() > MAX_DEPTH {
71        crate::error!(
72            "[PAINT][ORDER] 祖先チェーンが深すぎます: {} (上限 {})。own_z をそのまま使います",
73            ancestor_zs.len(),
74            MAX_DEPTH
75        );
76        return Err(PaintOrderError::DepthLimitExceeded(ancestor_zs.len()));
77    }
78    let mut acc: Option<i32> = None;
79    for &z in ancestor_zs {
80        acc = Some(effective_z(z, acc));
81    }
82    Ok(effective_z(own_z, acc))
83}