|
| 1 | +use ie_css::CssValue; |
| 2 | +use ie_css::resolve::ResolvedStyle; |
| 3 | +use ie_css::values::PropertyId; |
| 4 | + |
| 5 | +use crate::box_generation::{get_px, get_px_or, is_auto}; |
| 6 | +use crate::text_measure::TextMeasure; |
| 7 | +use crate::{BoxType, LayoutTree, Rect}; |
| 8 | + |
| 9 | +/// Lay out a block-level box and all its descendants. |
| 10 | +pub fn layout_block( |
| 11 | + box_idx: usize, |
| 12 | + tree: &mut LayoutTree, |
| 13 | + styles: &[ResolvedStyle], |
| 14 | + containing_width: f32, |
| 15 | + offset_y: f32, |
| 16 | + text_measure: &dyn TextMeasure, |
| 17 | +) { |
| 18 | + let (node_id, _box_type) = { |
| 19 | + let b = &tree.boxes[box_idx]; |
| 20 | + (b.node_id, b.box_type.clone()) |
| 21 | + }; |
| 22 | + |
| 23 | + let style = node_id.and_then(|id| styles.get(id)); |
| 24 | + |
| 25 | + // --- 1. Compute width --- |
| 26 | + let margin_left; |
| 27 | + let margin_right; |
| 28 | + let padding_left; |
| 29 | + let padding_right; |
| 30 | + let border_left; |
| 31 | + let border_right; |
| 32 | + let content_width; |
| 33 | + |
| 34 | + if let Some(style) = style { |
| 35 | + padding_left = get_px(style, PropertyId::PaddingLeft); |
| 36 | + padding_right = get_px(style, PropertyId::PaddingRight); |
| 37 | + border_left = get_px(style, PropertyId::BorderLeftWidth); |
| 38 | + border_right = get_px(style, PropertyId::BorderRightWidth); |
| 39 | + |
| 40 | + let is_border_box = style |
| 41 | + .get(PropertyId::BoxSizing) |
| 42 | + .is_some_and(|v| matches!(v, CssValue::Keyword(k) if k == "border-box")); |
| 43 | + |
| 44 | + let specified_width = if is_auto(style, PropertyId::Width) { |
| 45 | + None |
| 46 | + } else { |
| 47 | + Some(get_px(style, PropertyId::Width)) |
| 48 | + }; |
| 49 | + |
| 50 | + let auto_margin_left = is_auto(style, PropertyId::MarginLeft); |
| 51 | + let auto_margin_right = is_auto(style, PropertyId::MarginRight); |
| 52 | + |
| 53 | + let non_content = padding_left + padding_right + border_left + border_right; |
| 54 | + |
| 55 | + match specified_width { |
| 56 | + Some(w) => { |
| 57 | + let box_width = if is_border_box { w } else { w + non_content }; |
| 58 | + let remaining = containing_width - box_width; |
| 59 | + if auto_margin_left && auto_margin_right { |
| 60 | + let m = (remaining / 2.0).max(0.0); |
| 61 | + margin_left = m; |
| 62 | + margin_right = m; |
| 63 | + } else if auto_margin_left { |
| 64 | + margin_right = get_px(style, PropertyId::MarginRight); |
| 65 | + margin_left = (remaining - margin_right).max(0.0); |
| 66 | + } else if auto_margin_right { |
| 67 | + margin_left = get_px(style, PropertyId::MarginLeft); |
| 68 | + margin_right = (remaining - margin_left).max(0.0); |
| 69 | + } else { |
| 70 | + margin_left = get_px(style, PropertyId::MarginLeft); |
| 71 | + margin_right = get_px(style, PropertyId::MarginRight); |
| 72 | + } |
| 73 | + content_width = if is_border_box { |
| 74 | + (w - padding_left - padding_right - border_left - border_right).max(0.0) |
| 75 | + } else { |
| 76 | + w |
| 77 | + }; |
| 78 | + } |
| 79 | + None => { |
| 80 | + margin_left = if auto_margin_left { |
| 81 | + 0.0 |
| 82 | + } else { |
| 83 | + get_px(style, PropertyId::MarginLeft) |
| 84 | + }; |
| 85 | + margin_right = if auto_margin_right { |
| 86 | + 0.0 |
| 87 | + } else { |
| 88 | + get_px(style, PropertyId::MarginRight) |
| 89 | + }; |
| 90 | + content_width = |
| 91 | + (containing_width - margin_left - margin_right - non_content).max(0.0); |
| 92 | + } |
| 93 | + } |
| 94 | + } else { |
| 95 | + // Anonymous box — takes full containing width |
| 96 | + margin_left = 0.0; |
| 97 | + margin_right = 0.0; |
| 98 | + padding_left = 0.0; |
| 99 | + padding_right = 0.0; |
| 100 | + border_left = 0.0; |
| 101 | + border_right = 0.0; |
| 102 | + content_width = containing_width; |
| 103 | + } |
| 104 | + |
| 105 | + // Apply min/max width constraints |
| 106 | + let content_width = if let Some(style) = style { |
| 107 | + let min_w = get_px(style, PropertyId::MinWidth); |
| 108 | + let max_w = get_px_or(style, PropertyId::MaxWidth, f32::MAX); |
| 109 | + content_width.max(min_w).min(max_w) |
| 110 | + } else { |
| 111 | + content_width |
| 112 | + }; |
| 113 | + |
| 114 | + // Set box position and dimensions |
| 115 | + let x = margin_left + padding_left + border_left; |
| 116 | + let y = offset_y |
| 117 | + + tree.boxes[box_idx].margin.top |
| 118 | + + tree.boxes[box_idx].padding.top |
| 119 | + + tree.boxes[box_idx].border.top; |
| 120 | + |
| 121 | + tree.boxes[box_idx].content_rect.x = x; |
| 122 | + tree.boxes[box_idx].content_rect.y = y; |
| 123 | + tree.boxes[box_idx].content_rect.width = content_width; |
| 124 | + tree.boxes[box_idx].margin.left = margin_left; |
| 125 | + tree.boxes[box_idx].margin.right = margin_right; |
| 126 | + tree.boxes[box_idx].padding.left = padding_left; |
| 127 | + tree.boxes[box_idx].padding.right = padding_right; |
| 128 | + tree.boxes[box_idx].border.left = border_left; |
| 129 | + tree.boxes[box_idx].border.right = border_right; |
| 130 | + |
| 131 | + // --- 2. Layout children --- |
| 132 | + let children = tree.boxes[box_idx].children.clone(); |
| 133 | + let content_y = tree.boxes[box_idx].content_rect.y; |
| 134 | + let mut child_y = content_y; |
| 135 | + let mut prev_margin_bottom: f32 = 0.0; |
| 136 | + |
| 137 | + for (i, &child_idx) in children.iter().enumerate() { |
| 138 | + // Margin collapsing between siblings |
| 139 | + let child_margin_top = tree.boxes[child_idx].margin.top; |
| 140 | + if i > 0 { |
| 141 | + let collapsed = prev_margin_bottom.max(child_margin_top); |
| 142 | + child_y -= prev_margin_bottom; // undo previous margin |
| 143 | + child_y += collapsed; // apply collapsed margin |
| 144 | + } else { |
| 145 | + child_y += child_margin_top; |
| 146 | + } |
| 147 | + |
| 148 | + match &tree.boxes[child_idx].box_type { |
| 149 | + BoxType::Block | BoxType::Anonymous => { |
| 150 | + layout_block( |
| 151 | + child_idx, |
| 152 | + tree, |
| 153 | + styles, |
| 154 | + content_width, |
| 155 | + child_y, |
| 156 | + text_measure, |
| 157 | + ); |
| 158 | + } |
| 159 | + BoxType::Text(text) => { |
| 160 | + // Text nodes don't have resolved styles; inherit from parent |
| 161 | + let font_size = node_id |
| 162 | + .and_then(|id| styles.get(id)) |
| 163 | + .map(|s| get_px(s, PropertyId::FontSize)) |
| 164 | + .filter(|&v| v > 0.0) |
| 165 | + .unwrap_or(16.0); |
| 166 | + let text = text.clone(); |
| 167 | + let metrics = text_measure.measure(&text, font_size); |
| 168 | + tree.boxes[child_idx].content_rect = Rect { |
| 169 | + x: tree.boxes[box_idx].content_rect.x, |
| 170 | + y: child_y, |
| 171 | + width: metrics.width, |
| 172 | + height: metrics.height, |
| 173 | + }; |
| 174 | + } |
| 175 | + BoxType::Inline | BoxType::InlineBlock => { |
| 176 | + // Simplified: treat as block for now |
| 177 | + layout_block( |
| 178 | + child_idx, |
| 179 | + tree, |
| 180 | + styles, |
| 181 | + content_width, |
| 182 | + child_y, |
| 183 | + text_measure, |
| 184 | + ); |
| 185 | + } |
| 186 | + } |
| 187 | + |
| 188 | + let child_height = tree.boxes[child_idx].content_rect.height |
| 189 | + + tree.boxes[child_idx].padding.top |
| 190 | + + tree.boxes[child_idx].padding.bottom |
| 191 | + + tree.boxes[child_idx].border.top |
| 192 | + + tree.boxes[child_idx].border.bottom; |
| 193 | + child_y += child_height; |
| 194 | + prev_margin_bottom = tree.boxes[child_idx].margin.bottom; |
| 195 | + } |
| 196 | + |
| 197 | + child_y += prev_margin_bottom; // final child's bottom margin |
| 198 | + |
| 199 | + // --- 3. Compute height --- |
| 200 | + let auto_height = child_y - content_y; |
| 201 | + let content_height = if let Some(style) = style { |
| 202 | + if is_auto(style, PropertyId::Height) { |
| 203 | + auto_height |
| 204 | + } else { |
| 205 | + let h = get_px(style, PropertyId::Height); |
| 206 | + let min_h = get_px(style, PropertyId::MinHeight); |
| 207 | + let max_h = get_px_or(style, PropertyId::MaxHeight, f32::MAX); |
| 208 | + h.max(min_h).min(max_h) |
| 209 | + } |
| 210 | + } else { |
| 211 | + auto_height |
| 212 | + }; |
| 213 | + |
| 214 | + tree.boxes[box_idx].content_rect.height = content_height; |
| 215 | +} |
0 commit comments