1use crate::box_model::{resolve_block_width, BoxModel};
7use crate::float::FloatContext;
8use crate::fragment::{Fragment, FragmentKind};
9use crate::geometry::{Point, Size};
10use crate::inline;
11use crate::layout::{self, LayoutContext};
12use crate::style::{BoxSizing, Float};
13use crate::tree::{FormattingContextType, NodeId};
14
15pub fn layout_block(
17 ctx: &LayoutContext,
18 node: NodeId,
19 containing_block_width: f32,
20 containing_block_height: f32,
21) -> Fragment {
22 let style = ctx.tree.style(node);
23 let mut fragment = Fragment::new(node, FragmentKind::Box);
24
25 let (content_width, margin) = resolve_block_width(style, containing_block_width);
27
28 let border = BoxModel::resolve_border(style);
30 let padding = BoxModel::resolve_padding(style, containing_block_width);
31
32 fragment.margin = margin;
33 fragment.border = border;
34 fragment.padding = padding;
35
36 let fc_type = ctx.tree.formatting_context(node);
38
39 let mut float_ctx = FloatContext::new(content_width);
40
41 let content_height = match fc_type {
42 FormattingContextType::Inline => layout_inline_children(
43 ctx,
44 node,
45 content_width,
46 &mut fragment.children,
47 &mut float_ctx,
48 ),
49 _ => layout_block_children(
50 ctx,
51 node,
52 content_width,
53 containing_block_height,
54 &mut fragment.children,
55 &mut float_ctx,
56 ),
57 };
58
59 let specified_height = style.height.resolve(containing_block_height);
61 let mut final_height = match specified_height {
62 Some(mut h) => {
63 if style.box_sizing == BoxSizing::BorderBox {
64 h = (h - border.vertical() - padding.vertical()).max(0.0);
65 }
66 h
67 }
68 None => content_height,
69 };
70
71 let min_h = style.min_height.resolve(containing_block_height);
73 let max_h = style
74 .max_height
75 .resolve(containing_block_height)
76 .unwrap_or(f32::INFINITY);
77 final_height = final_height.max(min_h).min(max_h);
78
79 if style.establishes_bfc() {
81 final_height = final_height.max(float_ctx.clear_all());
82 }
83
84 fragment.size = Size::new(content_width, final_height);
85
86 fragment
87}
88
89fn layout_block_children(
91 ctx: &LayoutContext,
92 parent: NodeId,
93 containing_width: f32,
94 containing_height: f32,
95 fragments: &mut Vec<Fragment>,
96 float_ctx: &mut FloatContext,
97) -> f32 {
98 let children = ctx.tree.children(parent);
99 let mut cursor_y: f32 = 0.0;
100 let mut prev_margin_bottom: f32 = 0.0;
101 let mut is_first_child = true;
102
103 for &child_id in children {
104 let child_style = ctx.tree.style(child_id);
105
106 if child_style.display.is_none() {
108 continue;
109 }
110
111 if child_style.position.is_absolutely_positioned() {
113 let mut child_fragment =
115 layout::layout_node(ctx, child_id, containing_width, containing_height);
116 child_fragment.position = Point::ZERO; fragments.push(child_fragment);
118 continue;
119 }
120
121 if child_style.float != Float::None {
123 let child_fragment =
124 layout::layout_node(ctx, child_id, containing_width, containing_height);
125 let floated = float_ctx.place_float(child_fragment, child_style.float, cursor_y);
126 fragments.push(floated);
127 continue;
128 }
129
130 if child_style.clear != crate::style::Clear::None {
132 let clear_y = float_ctx.clear(child_style.clear);
133 cursor_y = cursor_y.max(clear_y);
134 }
135
136 let mut child_fragment =
138 layout::layout_node(ctx, child_id, containing_width, containing_height);
139
140 let child_margin_top = child_fragment.margin.top;
142 let collapsed_margin = collapse_margins(prev_margin_bottom, child_margin_top);
143
144 if is_first_child {
145 cursor_y += child_margin_top;
148 } else {
149 cursor_y -= prev_margin_bottom;
151 cursor_y += collapsed_margin;
152 }
153
154 child_fragment.position = Point::new(
156 child_fragment.margin.left + child_fragment.border.left + child_fragment.padding.left
157 - child_fragment.border.left
158 - child_fragment.padding.left,
159 cursor_y,
160 );
161 child_fragment.position = Point::new(child_fragment.margin.left, cursor_y);
163
164 cursor_y += child_fragment.border_box().height;
166 prev_margin_bottom = child_fragment.margin.bottom;
167 is_first_child = false;
168
169 fragments.push(child_fragment);
170 }
171
172 cursor_y
174}
175
176fn layout_inline_children(
178 ctx: &LayoutContext,
179 parent: NodeId,
180 containing_width: f32,
181 fragments: &mut Vec<Fragment>,
182 float_ctx: &mut FloatContext,
183) -> f32 {
184 inline::layout_inline_formatting_context(ctx, parent, containing_width, fragments, float_ctx)
185}
186
187fn collapse_margins(margin_a: f32, margin_b: f32) -> f32 {
193 if margin_a >= 0.0 && margin_b >= 0.0 {
194 margin_a.max(margin_b)
195 } else if margin_a < 0.0 && margin_b < 0.0 {
196 margin_a.min(margin_b)
197 } else {
198 margin_a + margin_b
199 }
200}
201
202pub fn shrink_to_fit_width(ctx: &LayoutContext, node: NodeId, available_width: f32) -> f32 {
205 let preferred = compute_intrinsic_width(ctx, node, f32::INFINITY);
207 let preferred_min = compute_intrinsic_width(ctx, node, 0.0);
209
210 preferred_min.max(0.0).max(preferred.min(available_width))
212}
213
214fn compute_intrinsic_width(ctx: &LayoutContext, node: NodeId, available: f32) -> f32 {
216 let children = ctx.tree.children(node);
217 let style = ctx.tree.style(node);
218
219 let _border = BoxModel::resolve_border(style);
220 let _padding = BoxModel::resolve_padding(style, available);
221
222 let mut max_child_width: f32 = 0.0;
223
224 for &child_id in children {
225 let child_style = ctx.tree.style(child_id);
226 if child_style.display.is_none() || child_style.is_out_of_flow() {
227 continue;
228 }
229
230 if let Some(text) = ctx.tree.node(child_id).text_content() {
231 let size = ctx.text_measure.measure(text, 16.0, available);
232 max_child_width = max_child_width.max(size.width);
233 } else if child_style.display.is_block_level() {
234 let child_width = if let Some(w) = child_style.width.resolve(available) {
235 w
236 } else {
237 compute_intrinsic_width(ctx, child_id, available)
238 };
239 let child_box = BoxModel::resolve(child_style, available);
240 max_child_width =
241 max_child_width.max(child_width + child_box.horizontal_border_padding());
242 }
243 }
244
245 max_child_width
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use crate::layout::{compute_layout, FixedWidthTextMeasure};
252 use crate::style::ComputedStyle;
253 use crate::tree::BoxTreeBuilder;
254 use crate::values::{LengthPercentage, LengthPercentageAuto};
255
256 #[test]
257 fn test_margin_collapsing_positive() {
258 assert_eq!(collapse_margins(10.0, 20.0), 20.0);
259 }
260
261 #[test]
262 fn test_margin_collapsing_negative() {
263 assert_eq!(collapse_margins(-10.0, -20.0), -20.0);
264 }
265
266 #[test]
267 fn test_margin_collapsing_mixed() {
268 assert_eq!(collapse_margins(10.0, -5.0), 5.0);
269 }
270
271 #[test]
272 fn test_block_with_padding_and_children() {
273 let mut builder = BoxTreeBuilder::new();
274 let mut root_style = ComputedStyle::block();
275 root_style.padding_top = LengthPercentage::px(10.0);
276 root_style.padding_bottom = LengthPercentage::px(10.0);
277 let root = builder.root(root_style);
278
279 let mut child_style = ComputedStyle::block();
280 child_style.height = LengthPercentageAuto::px(100.0);
281 builder.element(root, child_style);
282
283 let tree = builder.build();
284 let result = compute_layout(&tree, &FixedWidthTextMeasure, Size::new(800.0, 600.0));
285
286 let root_rect = result.bounding_rect(tree.root()).unwrap();
287 assert_eq!(root_rect.height, 120.0);
289 }
290
291 #[test]
292 fn test_nested_blocks() {
293 let mut builder = BoxTreeBuilder::new();
294 let root = builder.root(ComputedStyle::block());
295
296 let mut outer_style = ComputedStyle::block();
297 outer_style.padding_left = LengthPercentage::px(20.0);
298 outer_style.padding_right = LengthPercentage::px(20.0);
299 let outer = builder.element(root, outer_style);
300
301 let mut inner_style = ComputedStyle::block();
302 inner_style.height = LengthPercentageAuto::px(50.0);
303 let inner = builder.element(outer, inner_style);
304
305 let tree = builder.build();
306 let result = compute_layout(&tree, &FixedWidthTextMeasure, Size::new(800.0, 600.0));
307
308 let inner_rect = result.bounding_rect(inner).unwrap();
309 assert_eq!(inner_rect.width, 760.0);
311 assert_eq!(inner_rect.x, 20.0);
313 }
314
315 #[test]
316 fn test_percentage_width() {
317 let mut builder = BoxTreeBuilder::new();
318 let root = builder.root(ComputedStyle::block());
319
320 let mut child_style = ComputedStyle::block();
321 child_style.width = LengthPercentageAuto::percent(50.0);
322 child_style.height = LengthPercentageAuto::px(100.0);
323 let child = builder.element(root, child_style);
324
325 let tree = builder.build();
326 let result = compute_layout(&tree, &FixedWidthTextMeasure, Size::new(800.0, 600.0));
327
328 let child_rect = result.bounding_rect(child).unwrap();
329 assert_eq!(child_rect.width, 400.0); }
331
332 #[test]
333 fn test_min_max_width() {
334 let mut builder = BoxTreeBuilder::new();
335 let root = builder.root(ComputedStyle::block());
336
337 let mut child_style = ComputedStyle::block();
338 child_style.width = LengthPercentageAuto::px(1000.0);
339 child_style.max_width = crate::values::LengthPercentageNone::px(500.0);
340 child_style.height = LengthPercentageAuto::px(50.0);
341 let child = builder.element(root, child_style);
342
343 let tree = builder.build();
344 let result = compute_layout(&tree, &FixedWidthTextMeasure, Size::new(800.0, 600.0));
345
346 let child_rect = result.bounding_rect(child).unwrap();
347 assert_eq!(child_rect.width, 500.0); }
349}