⎇ 9f1d71c97e0852760580b7d7bb30f503baa6a988
src/compute/flexbox.rs
Ñawpa willay
Pi imata llamkarqan
ch'uya
Fix intrinsic size of scroll containers that have a small explicit flex-basis (#728)
1//! Computes the [flexbox](https://css-tricks.com/snippets/css/a-guide-to-flexbox/) layout algorithm on [`TaffyTree`](crate::TaffyTree) according to the [spec](https://www.w3.org/TR/css-flexbox-1/)
2use crate::compute::common::alignment::compute_alignment_offset;
3use crate::geometry::{Line, Point, Rect, Size};
4use crate::style::{
5 AlignContent, AlignItems, AlignSelf, AvailableSpace, Dimension, FlexWrap, JustifyContent, LengthPercentageAuto,
6 Overflow, Position,
7};
8use crate::style::{CoreStyle, FlexDirection, FlexboxContainerStyle, FlexboxItemStyle};
9use crate::style_helpers::{TaffyMaxContent, TaffyMinContent};
10use crate::tree::{Layout, LayoutInput, LayoutOutput, RunMode, SizingMode};
11use crate::tree::{LayoutFlexboxContainer, LayoutPartialTreeExt, NodeId};
12use crate::util::debug::debug_log;
13use crate::util::sys::{f32_max, new_vec_with_capacity, Vec};
14use crate::util::MaybeMath;
15use crate::util::{MaybeResolve, ResolveOrZero};
16use crate::{BoxGenerationMode, BoxSizing};
17
18use super::common::alignment::apply_alignment_fallback;
19#[cfg(feature = "content_size")]
20use super::common::content_size::compute_content_size_contribution;
21
22/// The intermediate results of a flexbox calculation for a single item
23struct FlexItem {
24 /// The identifier for the associated node
25 node: NodeId,
26
27 /// The order of the node relative to it's siblings
28 order: u32,
29
30 /// The base size of this item
31 size: Size<Option<f32>>,
32 /// The minimum allowable size of this item
33 min_size: Size<Option<f32>>,
34 /// The maximum allowable size of this item
35 max_size: Size<Option<f32>>,
36 /// The cross-alignment of this item
37 align_self: AlignSelf,
38
39 /// The overflow style of the item
40 overflow: Point<Overflow>,
41 /// The width of the scrollbars (if it has any)
42 scrollbar_width: f32,
43 /// The flex shrink style of the item
44 flex_shrink: f32,
45 /// The flex grow style of the item
46 flex_grow: f32,
47
48 /// The minimum size of the item. This differs from min_size above because it also
49 /// takes into account content based automatic minimum sizes
50 resolved_minimum_main_size: f32,
51
52 /// The final offset of this item
53 inset: Rect<Option<f32>>,
54 /// The margin of this item
55 margin: Rect<f32>,
56 /// Whether each margin is an auto margin or not
57 margin_is_auto: Rect<bool>,
58 /// The padding of this item
59 padding: Rect<f32>,
60 /// The border of this item
61 border: Rect<f32>,
62
63 /// The default size of this item
64 flex_basis: f32,
65 /// The default size of this item, minus padding and border
66 inner_flex_basis: f32,
67 /// The amount by which this item has deviated from its target size
68 violation: f32,
69 /// Is the size of this item locked
70 frozen: bool,
71
72 /// Either the max- or min- content flex fraction
73 /// See https://www.w3.org/TR/css-flexbox-1/#intrinsic-main-sizes
74 content_flex_fraction: f32,
75
76 /// The proposed inner size of this item
77 hypothetical_inner_size: Size<f32>,
78 /// The proposed outer size of this item
79 hypothetical_outer_size: Size<f32>,
80 /// The size that this item wants to be
81 target_size: Size<f32>,
82 /// The size that this item wants to be, plus any padding and border
83 outer_target_size: Size<f32>,
84
85 /// The position of the bottom edge of this item
86 baseline: f32,
87
88 /// A temporary value for the main offset
89 ///
90 /// Offset is the relative position from the item's natural flow position based on
91 /// relative position values, alignment, and justification. Does not include margin/padding/border.
92 offset_main: f32,
93 /// A temporary value for the cross offset
94 ///
95 /// Offset is the relative position from the item's natural flow position based on
96 /// relative position values, alignment, and justification. Does not include margin/padding/border.
97 offset_cross: f32,
98}
99
100impl FlexItem {
101 /// Returns true if the item is a <https://www.w3.org/TR/css-overflow-3/#scroll-container>
102 fn is_scroll_container(&self) -> bool {
103 self.overflow.x.is_scroll_container() | self.overflow.y.is_scroll_container()
104 }
105}
106
107/// A line of [`FlexItem`] used for intermediate computation
108struct FlexLine<'a> {
109 /// The slice of items to iterate over during computation of this line
110 items: &'a mut [FlexItem],
111 /// The dimensions of the cross-axis
112 cross_size: f32,
113 /// The relative offset of the cross-axis
114 offset_cross: f32,
115}
116
117/// Values that can be cached during the flexbox algorithm
118struct AlgoConstants {
119 /// The direction of the current segment being laid out
120 dir: FlexDirection,
121 /// Is this segment a row
122 is_row: bool,
123 /// Is this segment a column
124 is_column: bool,
125 /// Is wrapping enabled (in either direction)
126 is_wrap: bool,
127 /// Is the wrap direction inverted
128 is_wrap_reverse: bool,
129
130 /// The item's min_size style
131 min_size: Size<Option<f32>>,
132 /// The item's max_size style
133 max_size: Size<Option<f32>>,
134 /// The margin of this section
135 margin: Rect<f32>,
136 /// The border of this section
137 border: Rect<f32>,
138 /// The space between the content box and the border box.
139 /// This consists of padding + border + scrollbar_gutter.
140 content_box_inset: Rect<f32>,
141 /// The size reserved for scrollbar gutters in each axis
142 scrollbar_gutter: Point<f32>,
143 /// The gap of this section
144 gap: Size<f32>,
145 /// The align_items property of this node
146 align_items: AlignItems,
147 /// The align_content property of this node
148 align_content: AlignContent,
149 /// The justify_content property of this node
150 justify_content: Option<JustifyContent>,
151
152 /// The border-box size of the node being laid out (if known)
153 node_outer_size: Size<Option<f32>>,
154 /// The content-box size of the node being laid out (if known)
155 node_inner_size: Size<Option<f32>>,
156
157 /// The size of the virtual container containing the flex items.
158 container_size: Size<f32>,
159 /// The size of the internal container
160 inner_container_size: Size<f32>,
161}
162
163/// Computes the layout of a box according to the flexbox algorithm
164pub fn compute_flexbox_layout(
165 tree: &mut impl LayoutFlexboxContainer,
166 node: NodeId,
167 inputs: LayoutInput,
168) -> LayoutOutput {
169 let LayoutInput { known_dimensions, parent_size, run_mode, .. } = inputs;
170 let style = tree.get_flexbox_container_style(node);
171
172 // Pull these out earlier to avoid borrowing issues
173 let aspect_ratio = style.aspect_ratio();
174 let padding = style.padding().resolve_or_zero(parent_size.width);
175 let border = style.border().resolve_or_zero(parent_size.width);
176 let padding_border_sum = padding.sum_axes() + border.sum_axes();
177 let box_sizing_adjustment =
178 if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
179
180 let min_size = style
181 .min_size()
182 .maybe_resolve(parent_size)
183 .maybe_apply_aspect_ratio(aspect_ratio)
184 .maybe_add(box_sizing_adjustment);
185 let max_size = style
186 .max_size()
187 .maybe_resolve(parent_size)
188 .maybe_apply_aspect_ratio(aspect_ratio)
189 .maybe_add(box_sizing_adjustment);
190 let clamped_style_size = if inputs.sizing_mode == SizingMode::InherentSize {
191 style
192 .size()
193 .maybe_resolve(parent_size)
194 .maybe_apply_aspect_ratio(aspect_ratio)
195 .maybe_add(box_sizing_adjustment)
196 .maybe_clamp(min_size, max_size)
197 } else {
198 Size::NONE
199 };
200
201 // If both min and max in a given axis are set and max <= min then this determines the size in that axis
202 let min_max_definite_size = min_size.zip_map(max_size, |min, max| match (min, max) {
203 (Some(min), Some(max)) if max <= min => Some(min),
204 _ => None,
205 });
206
207 // The size of the container should be floored by the padding and border
208 let styled_based_known_dimensions =
209 known_dimensions.or(min_max_definite_size.or(clamped_style_size).maybe_max(padding_border_sum));
210
211 // Short-circuit layout if the container's size is fully determined by the container's size and the run mode
212 // is ComputeSize (and thus the container's size is all that we're interested in)
213 if run_mode == RunMode::ComputeSize {
214 if let Size { width: Some(width), height: Some(height) } = styled_based_known_dimensions {
215 return LayoutOutput::from_outer_size(Size { width, height });
216 }
217 }
218
219 debug_log!("FLEX:", dbg:style.flex_direction());
220 drop(style);
221
222 compute_preliminary(tree, node, LayoutInput { known_dimensions: styled_based_known_dimensions, ..inputs })
223}
224
225/// Compute a preliminary size for an item
226fn compute_preliminary(tree: &mut impl LayoutFlexboxContainer, node: NodeId, inputs: LayoutInput) -> LayoutOutput {
227 let LayoutInput { known_dimensions, parent_size, available_space, run_mode, .. } = inputs;
228
229 // Define some general constants we will need for the remainder of the algorithm.
230 let mut constants = compute_constants(tree.get_flexbox_container_style(node), known_dimensions, parent_size);
231
232 // 9. Flex Layout Algorithm
233
234 // 9.1. Initial Setup
235
236 // 1. Generate anonymous flex items as described in §4 Flex Items.
237 debug_log!("generate_anonymous_flex_items");
238 let mut flex_items = generate_anonymous_flex_items(tree, node, &constants);
239
240 // 9.2. Line Length Determination
241
242 // 2. Determine the available main and cross space for the flex items
243 debug_log!("determine_available_space");
244 let available_space = determine_available_space(known_dimensions, available_space, &constants);
245
246 // 3. Determine the flex base size and hypothetical main size of each item.
247 debug_log!("determine_flex_base_size");
248 determine_flex_base_size(tree, &constants, available_space, &mut flex_items);
249
250 #[cfg(feature = "debug")]
251 for item in flex_items.iter() {
252 debug_log!("item.flex_basis", item.flex_basis);
253 debug_log!("item.inner_flex_basis", item.inner_flex_basis);
254 debug_log!("item.hypothetical_outer_size", dbg:item.hypothetical_outer_size);
255 debug_log!("item.hypothetical_inner_size", dbg:item.hypothetical_inner_size);
256 debug_log!("item.resolved_minimum_main_size", dbg:item.resolved_minimum_main_size);
257 }
258
259 // 4. Determine the main size of the flex container
260 // This has already been done as part of compute_constants. The inner size is exposed as constants.node_inner_size.
261
262 // 9.3. Main Size Determination
263
264 // 5. Collect flex items into flex lines.
265 debug_log!("collect_flex_lines");
266 let mut flex_lines = collect_flex_lines(&constants, available_space, &mut flex_items);
267
268 // If container size is undefined, determine the container's main size
269 // and then re-resolve gaps based on newly determined size
270 debug_log!("determine_container_main_size");
271 if let Some(inner_main_size) = constants.node_inner_size.main(constants.dir) {
272 let outer_main_size = inner_main_size + constants.content_box_inset.main_axis_sum(constants.dir);
273 constants.inner_container_size.set_main(constants.dir, inner_main_size);
274 constants.container_size.set_main(constants.dir, outer_main_size);
275 } else {
276 // Sets constants.container_size and constants.outer_container_size
277 determine_container_main_size(tree, available_space, &mut flex_lines, &mut constants);
278 constants.node_inner_size.set_main(constants.dir, Some(constants.inner_container_size.main(constants.dir)));
279 constants.node_outer_size.set_main(constants.dir, Some(constants.container_size.main(constants.dir)));
280
281 debug_log!("constants.node_outer_size", dbg:constants.node_outer_size);
282 debug_log!("constants.node_inner_size", dbg:constants.node_inner_size);
283
284 // Re-resolve percentage gaps
285 let style = tree.get_flexbox_container_style(node);
286 let inner_container_size = constants.inner_container_size.main(constants.dir);
287 let new_gap = style.gap().main(constants.dir).maybe_resolve(inner_container_size).unwrap_or(0.0);
288 constants.gap.set_main(constants.dir, new_gap);
289 }
290
291 // 6. Resolve the flexible lengths of all the flex items to find their used main size.
292 debug_log!("resolve_flexible_lengths");
293 for line in &mut flex_lines {
294 resolve_flexible_lengths(line, &constants);
295 }
296
297 // 9.4. Cross Size Determination
298
299 // 7. Determine the hypothetical cross size of each item.
300 debug_log!("determine_hypothetical_cross_size");
301 for line in &mut flex_lines {
302 determine_hypothetical_cross_size(tree, line, &constants, available_space);
303 }
304
305 // Calculate child baselines. This function is internally smart and only computes child baselines
306 // if they are necessary.
307 debug_log!("calculate_children_base_lines");
308 calculate_children_base_lines(tree, known_dimensions, available_space, &mut flex_lines, &constants);
309
310 // 8. Calculate the cross size of each flex line.
311 debug_log!("calculate_cross_size");
312 calculate_cross_size(&mut flex_lines, known_dimensions, &constants);
313
314 // 9. Handle 'align-content: stretch'.
315 debug_log!("handle_align_content_stretch");
316 handle_align_content_stretch(&mut flex_lines, known_dimensions, &constants);
317
318 // 10. Collapse visibility:collapse items. If any flex items have visibility: collapse,
319 // note the cross size of the line they’re in as the item’s strut size, and restart
320 // layout from the beginning.
321 //
322 // In this second layout round, when collecting items into lines, treat the collapsed
323 // items as having zero main size. For the rest of the algorithm following that step,
324 // ignore the collapsed items entirely (as if they were display:none) except that after
325 // calculating the cross size of the lines, if any line’s cross size is less than the
326 // largest strut size among all the collapsed items in the line, set its cross size to
327 // that strut size.
328 //
329 // Skip this step in the second layout round.
330
331 // TODO implement once (if ever) we support visibility:collapse
332
333 // 11. Determine the used cross size of each flex item.
334 debug_log!("determine_used_cross_size");
335 determine_used_cross_size(tree, &mut flex_lines, &constants);
336
337 // 9.5. Main-Axis Alignment
338
339 // 12. Distribute any remaining free space.
340 debug_log!("distribute_remaining_free_space");
341 distribute_remaining_free_space(&mut flex_lines, &constants);
342
343 // 9.6. Cross-Axis Alignment
344
345 // 13. Resolve cross-axis auto margins (also includes 14).
346 debug_log!("resolve_cross_axis_auto_margins");
347 resolve_cross_axis_auto_margins(&mut flex_lines, &constants);
348
349 // 15. Determine the flex container’s used cross size.
350 debug_log!("determine_container_cross_size");
351 let total_line_cross_size = determine_container_cross_size(&flex_lines, known_dimensions, &mut constants);
352
353 // We have the container size.
354 // If our caller does not care about performing layout we are done now.
355 if run_mode == RunMode::ComputeSize {
356 return LayoutOutput::from_outer_size(constants.container_size);
357 }
358
359 // 16. Align all flex lines per align-content.
360 debug_log!("align_flex_lines_per_align_content");
361 align_flex_lines_per_align_content(&mut flex_lines, &constants, total_line_cross_size);
362
363 // Do a final layout pass and gather the resulting layouts
364 debug_log!("final_layout_pass");
365 let inflow_content_size = final_layout_pass(tree, &mut flex_lines, &constants);
366
367 // Before returning we perform absolute layout on all absolutely positioned children
368 debug_log!("perform_absolute_layout_on_absolute_children");
369 let absolute_content_size = perform_absolute_layout_on_absolute_children(tree, node, &constants);
370
371 debug_log!("hidden_layout");
372 let len = tree.child_count(node);
373 for order in 0..len {
374 let child = tree.get_child_id(node, order);
375 if tree.get_flexbox_child_style(child).box_generation_mode() == BoxGenerationMode::None {
376 tree.set_unrounded_layout(child, &Layout::with_order(order as u32));
377 tree.perform_child_layout(
378 child,
379 Size::NONE,
380 Size::NONE,
381 Size::MAX_CONTENT,
382 SizingMode::InherentSize,
383 Line::FALSE,
384 );
385 }
386 }
387
388 // 8.5. Flex Container Baselines: calculate the flex container's first baseline
389 // See https://www.w3.org/TR/css-flexbox-1/#flex-baselines
390 let first_vertical_baseline = if flex_lines.is_empty() {
391 None
392 } else {
393 flex_lines[0]
394 .items
395 .iter()
396 .find(|item| constants.is_column || item.align_self == AlignSelf::Baseline)
397 .or_else(|| flex_lines[0].items.iter().next())
398 .map(|child| {
399 let offset_vertical = if constants.is_row { child.offset_cross } else { child.offset_main };
400 offset_vertical + child.baseline
401 })
402 };
403
404 LayoutOutput::from_sizes_and_baselines(
405 constants.container_size,
406 inflow_content_size.f32_max(absolute_content_size),
407 Point { x: None, y: first_vertical_baseline },
408 )
409}
410
411/// Compute constants that can be reused during the flexbox algorithm.
412#[inline]
413fn compute_constants(
414 style: impl FlexboxContainerStyle,
415 known_dimensions: Size<Option<f32>>,
416 parent_size: Size<Option<f32>>,
417) -> AlgoConstants {
418 let dir = style.flex_direction();
419 let is_row = dir.is_row();
420 let is_column = dir.is_column();
421 let is_wrap = matches!(style.flex_wrap(), FlexWrap::Wrap | FlexWrap::WrapReverse);
422 let is_wrap_reverse = style.flex_wrap() == FlexWrap::WrapReverse;
423
424 let aspect_ratio = style.aspect_ratio();
425 let margin = style.margin().resolve_or_zero(parent_size.width);
426 let padding = style.padding().resolve_or_zero(parent_size.width);
427 let border = style.border().resolve_or_zero(parent_size.width);
428 let padding_border_sum = padding.sum_axes() + border.sum_axes();
429 let box_sizing_adjustment =
430 if style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
431
432 let align_items = style.align_items().unwrap_or(AlignItems::Stretch);
433 let align_content = style.align_content().unwrap_or(AlignContent::Stretch);
434 let justify_content = style.justify_content();
435
436 // Scrollbar gutters are reserved when the `overflow` property is set to `Overflow::Scroll`.
437 // However, the axis are switched (transposed) because a node that scrolls vertically needs
438 // *horizontal* space to be reserved for a scrollbar
439 let scrollbar_gutter = style.overflow().transpose().map(|overflow| match overflow {
440 Overflow::Scroll => style.scrollbar_width(),
441 _ => 0.0,
442 });
443 // TODO: make side configurable based on the `direction` property
444 let mut content_box_inset = padding + border;
445 content_box_inset.right += scrollbar_gutter.x;
446 content_box_inset.bottom += scrollbar_gutter.y;
447
448 let node_outer_size = known_dimensions;
449 let node_inner_size = node_outer_size.maybe_sub(content_box_inset.sum_axes());
450 let gap = style.gap().resolve_or_zero(node_inner_size.or(Size::zero()));
451
452 let container_size = Size::zero();
453 let inner_container_size = Size::zero();
454
455 AlgoConstants {
456 dir,
457 is_row,
458 is_column,
459 is_wrap,
460 is_wrap_reverse,
461 min_size: style
462 .min_size()
463 .maybe_resolve(parent_size)
464 .maybe_apply_aspect_ratio(aspect_ratio)
465 .maybe_add(box_sizing_adjustment),
466 max_size: style
467 .max_size()
468 .maybe_resolve(parent_size)
469 .maybe_apply_aspect_ratio(aspect_ratio)
470 .maybe_add(box_sizing_adjustment),
471 margin,
472 border,
473 gap,
474 content_box_inset,
475 scrollbar_gutter,
476 align_items,
477 align_content,
478 justify_content,
479 node_outer_size,
480 node_inner_size,
481 container_size,
482 inner_container_size,
483 }
484}
485
486/// Generate anonymous flex items.
487///
488/// # [9.1. Initial Setup](https://www.w3.org/TR/css-flexbox-1/#box-manip)
489///
490/// - [**Generate anonymous flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-anon-box) as described in [§4 Flex Items](https://www.w3.org/TR/css-flexbox-1/#flex-items).
491#[inline]
492fn generate_anonymous_flex_items(
493 tree: &impl LayoutFlexboxContainer,
494 node: NodeId,
495 constants: &AlgoConstants,
496) -> Vec<FlexItem> {
497 tree.child_ids(node)
498 .enumerate()
499 .map(|(index, child)| (index, child, tree.get_flexbox_child_style(child)))
500 .filter(|(_, _, style)| style.position() != Position::Absolute)
501 .filter(|(_, _, style)| style.box_generation_mode() != BoxGenerationMode::None)
502 .map(|(index, child, child_style)| {
503 let aspect_ratio = child_style.aspect_ratio();
504 let padding = child_style.padding().resolve_or_zero(constants.node_inner_size.width);
505 let border = child_style.border().resolve_or_zero(constants.node_inner_size.width);
506 let pb_sum = (padding + border).sum_axes();
507 let box_sizing_adjustment =
508 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
509 FlexItem {
510 node: child,
511 order: index as u32,
512 size: child_style
513 .size()
514 .maybe_resolve(constants.node_inner_size)
515 .maybe_apply_aspect_ratio(aspect_ratio)
516 .maybe_add(box_sizing_adjustment),
517 min_size: child_style
518 .min_size()
519 .maybe_resolve(constants.node_inner_size)
520 .maybe_apply_aspect_ratio(aspect_ratio)
521 .maybe_add(box_sizing_adjustment),
522 max_size: child_style
523 .max_size()
524 .maybe_resolve(constants.node_inner_size)
525 .maybe_apply_aspect_ratio(aspect_ratio)
526 .maybe_add(box_sizing_adjustment),
527
528 inset: child_style.inset().zip_size(constants.node_inner_size, |p, s| p.maybe_resolve(s)),
529 margin: child_style.margin().resolve_or_zero(constants.node_inner_size.width),
530 margin_is_auto: child_style.margin().map(|m| m == LengthPercentageAuto::Auto),
531 padding: child_style.padding().resolve_or_zero(constants.node_inner_size.width),
532 border: child_style.border().resolve_or_zero(constants.node_inner_size.width),
533 align_self: child_style.align_self().unwrap_or(constants.align_items),
534 overflow: child_style.overflow(),
535 scrollbar_width: child_style.scrollbar_width(),
536 flex_grow: child_style.flex_grow(),
537 flex_shrink: child_style.flex_shrink(),
538 flex_basis: 0.0,
539 inner_flex_basis: 0.0,
540 violation: 0.0,
541 frozen: false,
542
543 resolved_minimum_main_size: 0.0,
544 hypothetical_inner_size: Size::zero(),
545 hypothetical_outer_size: Size::zero(),
546 target_size: Size::zero(),
547 outer_target_size: Size::zero(),
548 content_flex_fraction: 0.0,
549
550 baseline: 0.0,
551
552 offset_main: 0.0,
553 offset_cross: 0.0,
554 }
555 })
556 .collect()
557}
558
559/// Determine the available main and cross space for the flex items.
560///
561/// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
562///
563/// - [**Determine the available main and cross space for the flex items**](https://www.w3.org/TR/css-flexbox-1/#algo-available).
564///
565/// For each dimension, if that dimension of the flex container’s content box is a definite size, use that;
566/// if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint;
567/// otherwise, subtract the flex container’s margin, border, and padding from the space available to the flex container in that dimension and use that value.
568/// **This might result in an infinite value**.
569#[inline]
570#[must_use]
571fn determine_available_space(
572 known_dimensions: Size<Option<f32>>,
573 outer_available_space: Size<AvailableSpace>,
574 constants: &AlgoConstants,
575) -> Size<AvailableSpace> {
576 // Note: min/max/preferred size styles have already been applied to known_dimensions in the `compute` function above
577 let width = match known_dimensions.width {
578 Some(node_width) => AvailableSpace::Definite(node_width - constants.content_box_inset.horizontal_axis_sum()),
579 None => outer_available_space
580 .width
581 .maybe_sub(constants.margin.horizontal_axis_sum())
582 .maybe_sub(constants.content_box_inset.horizontal_axis_sum()),
583 };
584
585 let height = match known_dimensions.height {
586 Some(node_height) => AvailableSpace::Definite(node_height - constants.content_box_inset.vertical_axis_sum()),
587 None => outer_available_space
588 .height
589 .maybe_sub(constants.margin.vertical_axis_sum())
590 .maybe_sub(constants.content_box_inset.vertical_axis_sum()),
591 };
592
593 Size { width, height }
594}
595
596/// Determine the flex base size and hypothetical main size of each item.
597///
598/// # [9.2. Line Length Determination](https://www.w3.org/TR/css-flexbox-1/#line-sizing)
599///
600/// - [**Determine the flex base size and hypothetical main size of each item:**](https://www.w3.org/TR/css-flexbox-1/#algo-main-item)
601///
602/// - A. If the item has a definite used flex basis, that’s the flex base size.
603///
604/// - B. If the flex item has ...
605///
606/// - an intrinsic aspect ratio,
607/// - a used flex basis of content, and
608/// - a definite cross size,
609///
610/// then the flex base size is calculated from its inner cross size and the flex item’s intrinsic aspect ratio.
611///
612/// - C. If the used flex basis is content or depends on its available space, and the flex container is being sized under a min-content
613/// or max-content constraint (e.g. when performing automatic table layout \[CSS21\]), size the item under that constraint.
614/// The flex base size is the item’s resulting main size.
615///
616/// - E. Otherwise, size the item into the available space using its used flex basis in place of its main size, treating a value of content as max-content.
617/// If a cross size is needed to determine the main size (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite,
618/// in this calculation use fit-content as the flex item’s cross size. The flex base size is the item’s resulting main size.
619///
620/// When determining the flex base size, the item’s min and max main sizes are ignored (no clamping occurs).
621/// Furthermore, the sizing calculations that floor the content box size at zero when applying box-sizing are also ignored.
622/// (For example, an item with a specified size of zero, positive padding, and box-sizing: border-box will have an outer flex base size of zero—and hence a negative inner flex base size.)
623#[inline]
624fn determine_flex_base_size(
625 tree: &mut impl LayoutFlexboxContainer,
626 constants: &AlgoConstants,
627 available_space: Size<AvailableSpace>,
628 flex_items: &mut [FlexItem],
629) {
630 let dir = constants.dir;
631
632 for child in flex_items.iter_mut() {
633 let child_style = tree.get_flexbox_child_style(child.node);
634
635 // Parent size for child sizing
636 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
637 let child_parent_size = Size::from_cross(dir, cross_axis_parent_size);
638
639 // Available space for child sizing
640 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
641 let child_min_cross = child.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
642 let child_max_cross = child.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
643 let cross_axis_available_space: AvailableSpace = available_space
644 .cross(dir)
645 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
646 .maybe_clamp(child_min_cross, child_max_cross);
647
648 // Known dimensions for child sizing
649 let child_known_dimensions = {
650 let mut ckd = child.size.with_main(dir, None);
651 if child.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
652 ckd.set_cross(
653 dir,
654 cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
655 );
656 }
657 ckd
658 };
659
660 let container_width = constants.node_inner_size.main(dir);
661 let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
662 let padding = child_style.padding().resolve_or_zero(container_width);
663 let border = child_style.border().resolve_or_zero(container_width);
664 (padding + border).sum_axes()
665 } else {
666 Size::ZERO
667 }
668 .main(dir);
669 let flex_basis = child_style.flex_basis().maybe_resolve(container_width).maybe_add(box_sizing_adjustment);
670
671 drop(child_style);
672
673 child.flex_basis = 'flex_basis: {
674 // A. If the item has a definite used flex basis, that’s the flex base size.
675
676 // B. If the flex item has an intrinsic aspect ratio,
677 // a used flex basis of content, and a definite cross size,
678 // then the flex base size is calculated from its inner
679 // cross size and the flex item’s intrinsic aspect ratio.
680
681 // Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
682 // So B will just work here by using main_size without special handling for aspect_ratio
683 let main_size = child.size.main(dir);
684 if let Some(flex_basis) = flex_basis.or(main_size) {
685 break 'flex_basis flex_basis;
686 };
687
688 // C. If the used flex basis is content or depends on its available space,
689 // and the flex container is being sized under a min-content or max-content
690 // constraint (e.g. when performing automatic table layout [CSS21]),
691 // size the item under that constraint. The flex base size is the item’s
692 // resulting main size.
693
694 // This is covered by the implementation of E below, which passes the available_space constraint
695 // through to the child size computation. It may need a separate implementation if/when D is implemented.
696
697 // D. Otherwise, if the used flex basis is content or depends on its
698 // available space, the available main size is infinite, and the flex item’s
699 // inline axis is parallel to the main axis, lay the item out using the rules
700 // for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
701 // is the item’s max-content main size.
702
703 // TODO if/when vertical writing modes are supported
704
705 // E. Otherwise, size the item into the available space using its used flex basis
706 // in place of its main size, treating a value of content as max-content.
707 // If a cross size is needed to determine the main size (e.g. when the
708 // flex item’s main size is in its block axis) and the flex item’s cross size
709 // is auto and not definite, in this calculation use fit-content as the
710 // flex item’s cross size. The flex base size is the item’s resulting main size.
711
712 let child_available_space = Size::MAX_CONTENT
713 .with_main(
714 dir,
715 // Map AvailableSpace::Definite to AvailableSpace::MaxContent
716 if available_space.main(dir) == AvailableSpace::MinContent {
717 AvailableSpace::MinContent
718 } else {
719 AvailableSpace::MaxContent
720 },
721 )
722 .with_cross(dir, cross_axis_available_space);
723
724 debug_log!("COMPUTE CHILD BASE SIZE:");
725 break 'flex_basis tree.measure_child_size(
726 child.node,
727 child_known_dimensions,
728 child_parent_size,
729 child_available_space,
730 SizingMode::ContentSize,
731 dir.main_axis(),
732 Line::FALSE,
733 );
734 };
735
736 // Floor flex-basis by the padding_border_sum (floors inner_flex_basis at zero)
737 // This seems to be in violation of the spec which explicitly states that the content box should not be floored at zero
738 // (like it usually is) when calculating the flex-basis. But including this matches both Chrome and Firefox's behaviour.
739 //
740 // TODO: resolve spec violation
741 // Spec: https://www.w3.org/TR/css-flexbox-1/#intrinsic-item-contributions
742 // Spec: https://www.w3.org/TR/css-flexbox-1/#change-2016-max-contribution
743 let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
744 child.flex_basis = child.flex_basis.max(padding_border_sum);
745
746 // The hypothetical main size is the item’s flex base size clamped according to its
747 // used min and max main sizes (and flooring the content box size at zero).
748
749 child.inner_flex_basis =
750 child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
751
752 let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
753
754 // Note that it is important that the `parent_size` parameter in the main axis is not set for this
755 // function call as it used for resolving percentages, and percentage size in an axis should not contribute
756 // to a min-content contribution in that same axis. However the `parent_size` and `available_space` *should*
757 // be set to their usual values in the cross axis so that wrapping content can wrap correctly.
758 //
759 // See https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
760 let style_min_main_size =
761 child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
762
763 child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
764 let min_content_main_size = {
765 let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
766
767 debug_log!("COMPUTE CHILD MIN SIZE:");
768 tree.measure_child_size(
769 child.node,
770 child_known_dimensions,
771 child_parent_size,
772 child_available_space,
773 SizingMode::ContentSize,
774 dir.main_axis(),
775 Line::FALSE,
776 )
777 };
778
779 // 4.5. Automatic Minimum Size of Flex Items
780 // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
781 let clamped_min_content_size =
782 min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
783 clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
784 });
785
786 let hypothetical_inner_min_main =
787 child.resolved_minimum_main_size.maybe_max(padding_border_axes_sums.main(constants.dir));
788 let hypothetical_inner_size =
789 child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), child.max_size.main(constants.dir));
790 let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
791
792 child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
793 child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
794 }
795}
796
797/// Collect flex items into flex lines.
798///
799/// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
800///
801/// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
802///
803/// - If the flex container is single-line, collect all the flex items into a single flex line.
804///
805/// - Otherwise, starting from the first uncollected item, collect consecutive items one by one until the first time that the next collected item would not fit into the flex container’s inner main size
806/// (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
807/// If the very first uncollected item wouldn't fit, collect just it into the line.
808///
809/// For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
810///
811/// Repeat until all flex items have been collected into flex lines.
812///
813/// **Note that the "collect as many" line will collect zero-sized flex items onto the end of the previous line even if the last non-zero item exactly "filled up" the line**.
814#[inline]
815fn collect_flex_lines<'a>(
816 constants: &AlgoConstants,
817 available_space: Size<AvailableSpace>,
818 flex_items: &'a mut Vec<FlexItem>,
819) -> Vec<FlexLine<'a>> {
820 if !constants.is_wrap {
821 let mut lines = new_vec_with_capacity(1);
822 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
823 lines
824 } else {
825 let main_axis_available_space = match constants.max_size.main(constants.dir) {
826 Some(max_size) => AvailableSpace::Definite(
827 available_space
828 .main(constants.dir)
829 .into_option()
830 .unwrap_or(max_size)
831 .maybe_max(constants.min_size.main(constants.dir)),
832 ),
833 None => available_space.main(constants.dir),
834 };
835
836 match main_axis_available_space {
837 // If we're sizing under a max-content constraint then the flex items will never wrap
838 // (at least for now - future extensions to the CSS spec may add provisions for forced wrap points)
839 AvailableSpace::MaxContent => {
840 let mut lines = new_vec_with_capacity(1);
841 lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
842 lines
843 }
844 // If flex-wrap is Wrap and we're sizing under a min-content constraint, then we take every possible wrapping opportunity
845 // and place each item in it's own line
846 AvailableSpace::MinContent => {
847 let mut lines = new_vec_with_capacity(flex_items.len());
848 let mut items = &mut flex_items[..];
849 while !items.is_empty() {
850 let (line_items, rest) = items.split_at_mut(1);
851 lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
852 items = rest;
853 }
854 lines
855 }
856 AvailableSpace::Definite(main_axis_available_space) => {
857 let mut lines = new_vec_with_capacity(1);
858 let mut flex_items = &mut flex_items[..];
859 let main_axis_gap = constants.gap.main(constants.dir);
860
861 while !flex_items.is_empty() {
862 // Find index of the first item in the next line
863 // (or the last item if all remaining items are in the current line)
864 let mut line_length = 0.0;
865 let index = flex_items
866 .iter()
867 .enumerate()
868 .find(|&(idx, child)| {
869 // Gaps only occur between items (not before the first one or after the last one)
870 // So first item in the line does not contribute a gap to the line length
871 let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
872 line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
873 line_length > main_axis_available_space && idx != 0
874 })
875 .map(|(idx, _)| idx)
876 .unwrap_or(flex_items.len());
877
878 let (items, rest) = flex_items.split_at_mut(index);
879 lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
880 flex_items = rest;
881 }
882 lines
883 }
884 }
885 }
886}
887
888/// Determine the container's main size (if not already known)
889fn determine_container_main_size(
890 tree: &mut impl LayoutFlexboxContainer,
891 available_space: Size<AvailableSpace>,
892 lines: &mut [FlexLine<'_>],
893 constants: &mut AlgoConstants,
894) {
895 let dir = constants.dir;
896 let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
897
898 let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
899 match available_space.main(dir) {
900 AvailableSpace::Definite(main_axis_available_space) => {
901 let longest_line_length: f32 = lines
902 .iter()
903 .map(|line| {
904 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
905 let total_target_size = line
906 .items
907 .iter()
908 .map(|child| {
909 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
910 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
911 + child.margin.main_axis_sum(constants.dir))
912 .max(padding_border_sum)
913 })
914 .sum::<f32>();
915 total_target_size + line_main_axis_gap
916 })
917 .max_by(|a, b| a.total_cmp(b))
918 .unwrap_or(0.0);
919 let size = longest_line_length + main_content_box_inset;
920 if lines.len() > 1 {
921 f32_max(size, main_axis_available_space)
922 } else {
923 size
924 }
925 }
926 AvailableSpace::MinContent if constants.is_wrap => {
927 let longest_line_length: f32 = lines
928 .iter()
929 .map(|line| {
930 let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
931 let total_target_size = line
932 .items
933 .iter()
934 .map(|child| {
935 let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
936 (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
937 + child.margin.main_axis_sum(constants.dir))
938 .max(padding_border_sum)
939 })
940 .sum::<f32>();
941 total_target_size + line_main_axis_gap
942 })
943 .max_by(|a, b| a.total_cmp(b))
944 .unwrap_or(0.0);
945 longest_line_length + main_content_box_inset
946 }
947 AvailableSpace::MinContent | AvailableSpace::MaxContent => {
948 // Define a base main_size variable. This is mutated once for iteration over the outer
949 // loop over the flex lines as:
950 // "The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line."
951 let mut main_size = 0.0;
952
953 for line in lines.iter_mut() {
954 for item in line.items.iter_mut() {
955 let style_min = item.min_size.main(constants.dir);
956 let style_preferred = item.size.main(constants.dir);
957 let style_max = item.max_size.main(constants.dir);
958
959 // The spec seems a bit unclear on this point (my initial reading was that the `.maybe_max(style_preferred)` should
960 // not be included here), however this matches both Chrome and Firefox as of 9th March 2023.
961 //
962 // Spec: https://www.w3.org/TR/css-flexbox-1/#intrinsic-item-contributions
963 // Spec modification: https://www.w3.org/TR/css-flexbox-1/#change-2016-max-contribution
964 // Issue: https://github.com/w3c/csswg-drafts/issues/1435
965 // Gentest: padding_border_overrides_size_flex_basis_0.html
966 let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
967 let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
968 let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
969
970 let min_main_size = style_min
971 .maybe_max(flex_basis_min)
972 .or(flex_basis_min)
973 .unwrap_or(item.resolved_minimum_main_size)
974 .max(item.resolved_minimum_main_size);
975 let max_main_size =
976 style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
977
978 let content_contribution = match (min_main_size, style_preferred, max_main_size) {
979 // If the clamping values are such that max <= min, then we can avoid the expensive step of computing the content size
980 // as we know that the clamping values will override it anyway
981 (min, Some(pref), max) if max <= min || max <= pref => {
982 pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
983 }
984 (min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
985
986 // Else compute the min- or -max content size and apply the full formula for computing the
987 // min- or max- content contribution
988 _ if item.is_scroll_container() => {
989 item.flex_basis + item.margin.main_axis_sum(constants.dir)
990 }
991 _ => {
992 // Parent size for child sizing
993 let cross_axis_parent_size = constants.node_inner_size.cross(dir);
994
995 // Available space for child sizing
996 let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
997 let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
998 let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
999 let cross_axis_available_space: AvailableSpace = available_space
1000 .cross(dir)
1001 .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
1002 .maybe_clamp(child_min_cross, child_max_cross);
1003
1004 let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
1005
1006 // Known dimensions for child sizing
1007 let child_known_dimensions = {
1008 let mut ckd = item.size.with_main(dir, None);
1009 if item.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
1010 ckd.set_cross(
1011 dir,
1012 cross_axis_available_space
1013 .into_option()
1014 .maybe_sub(item.margin.cross_axis_sum(dir)),
1015 );
1016 }
1017 ckd
1018 };
1019
1020 // Either the min- or max- content size depending on which constraint we are sizing under.
1021 // TODO: Optimise by using already computed values where available
1022 debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
1023 let content_main_size = tree.measure_child_size(
1024 item.node,
1025 child_known_dimensions,
1026 constants.node_inner_size,
1027 child_available_space,
1028 SizingMode::InherentSize,
1029 dir.main_axis(),
1030 Line::FALSE,
1031 ) + item.margin.main_axis_sum(constants.dir);
1032
1033 // This is somewhat bizarre in that it's asymmetrical depending whether the flex container is a column or a row.
1034 //
1035 // I *think* this might relate to https://drafts.csswg.org/css-flexbox-1/#algo-main-container:
1036 //
1037 // "The automatic block size of a block-level flex container is its max-content size."
1038 //
1039 // Which could suggest that flex-basis defining a vertical size does not shrink because it is in the block axis, and the automatic size
1040 // in the block axis is a MAX content size. Whereas a flex-basis defining a horizontal size does shrink because the automatic size in
1041 // inline axis is MIN content size (although I don't have a reference for that).
1042 //
1043 // Ultimately, this was not found by reading the spec, but by trial and error fixing tests to align with Webkit/Firefox output.
1044 // (see the `flex_basis_unconstraint_row` and `flex_basis_uncontraint_column` generated tests which demonstrate this)
1045 if constants.is_row {
1046 content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
1047 } else {
1048 content_main_size
1049 .max(item.flex_basis)
1050 .maybe_clamp(style_min, style_max)
1051 .max(main_content_box_inset)
1052 }
1053 }
1054 };
1055 item.content_flex_fraction = {
1056 let diff = content_contribution - item.flex_basis;
1057 if diff > 0.0 {
1058 diff / f32_max(1.0, item.flex_grow)
1059 } else if diff < 0.0 {
1060 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
1061 diff / scaled_shrink_factor
1062 } else {
1063 // We are assuming that diff is 0.0 here and that we haven't accidentally introduced a NaN
1064 0.0
1065 }
1066 };
1067 }
1068
1069 // TODO Spec says to scale everything by the line's max flex fraction. But neither Chrome nor firefox implement this
1070 // so we don't either. But if we did want to, we'd need this computation here (and to use it below):
1071 //
1072 // Within each line, find the largest max-content flex fraction among all the flex items.
1073 // let line_flex_fraction = line
1074 // .items
1075 // .iter()
1076 // .map(|item| item.content_flex_fraction)
1077 // .max_by(|a, b| a.total_cmp(b))
1078 // .unwrap_or(0.0); // Unwrap case never gets hit because there is always at least one item a line
1079
1080 // Add each item’s flex base size to the product of:
1081 // - its flex grow factor (or scaled flex shrink factor,if the chosen max-content flex fraction was negative)
1082 // - the chosen max-content flex fraction
1083 // then clamp that result by the max main size floored by the min main size.
1084 //
1085 // The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line.
1086 let item_main_size_sum = line
1087 .items
1088 .iter_mut()
1089 .map(|item| {
1090 let flex_fraction = item.content_flex_fraction;
1091 // let flex_fraction = line_flex_fraction;
1092
1093 let flex_contribution = if item.content_flex_fraction > 0.0 {
1094 f32_max(1.0, item.flex_grow) * flex_fraction
1095 } else if item.content_flex_fraction < 0.0 {
1096 let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
1097 scaled_shrink_factor * flex_fraction
1098 } else {
1099 0.0
1100 };
1101 let size = item.flex_basis + flex_contribution;
1102 item.outer_target_size.set_main(constants.dir, size);
1103 item.target_size.set_main(constants.dir, size);
1104 size
1105 })
1106 .sum::<f32>();
1107
1108 let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1109 main_size = f32_max(main_size, item_main_size_sum + gap_sum)
1110 }
1111
1112 main_size + main_content_box_inset
1113 }
1114 }
1115 });
1116
1117 let outer_main_size = outer_main_size
1118 .maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
1119 .max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
1120
1121 // let outer_main_size = inner_main_size + constants.padding_border.main_axis_sum(constants.dir);
1122 let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
1123 constants.container_size.set_main(constants.dir, outer_main_size);
1124 constants.inner_container_size.set_main(constants.dir, inner_main_size);
1125 constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
1126}
1127
1128/// Resolve the flexible lengths of the items within a flex line.
1129/// Sets the `main` component of each item's `target_size` and `outer_target_size`
1130///
1131/// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
1132#[inline]
1133fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants) {
1134 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1135
1136 // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
1137 // items on the line. If the sum is less than the flex container’s inner main size,
1138 // use the flex grow factor for the rest of this algorithm; otherwise, use the
1139 // flex shrink factor.
1140
1141 let total_hypothetical_outer_main_size =
1142 line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
1143 let used_flex_factor: f32 = total_main_axis_gap + total_hypothetical_outer_main_size;
1144 let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1145 let shrinking = used_flex_factor > constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1146 let exactly_sized = !growing & !shrinking;
1147
1148 // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
1149 // - Any item that has a flex factor of zero
1150 // - If using the flex grow factor: any item that has a flex base size
1151 // greater than its hypothetical main size
1152 // - If using the flex shrink factor: any item that has a flex base size
1153 // smaller than its hypothetical main size
1154
1155 for child in line.items.iter_mut() {
1156 let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
1157 child.target_size.set_main(constants.dir, inner_target_size);
1158
1159 if exactly_sized
1160 || (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1161 || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1162 || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1163 {
1164 child.frozen = true;
1165 let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1166 child.outer_target_size.set_main(constants.dir, outer_target_size);
1167 }
1168 }
1169
1170 if exactly_sized {
1171 return;
1172 }
1173
1174 // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
1175 // and subtract this from the flex container’s inner main size. For frozen items,
1176 // use their outer target main size; for other items, use their outer flex base size.
1177
1178 let used_space: f32 = total_main_axis_gap
1179 + line
1180 .items
1181 .iter()
1182 .map(|child| {
1183 if child.frozen {
1184 child.outer_target_size.main(constants.dir)
1185 } else {
1186 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1187 }
1188 })
1189 .sum::<f32>();
1190
1191 let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1192
1193 // 4. Loop
1194
1195 loop {
1196 // a. Check for flexible items. If all the flex items on the line are frozen,
1197 // free space has been distributed; exit this loop.
1198
1199 if line.items.iter().all(|child| child.frozen) {
1200 break;
1201 }
1202
1203 // b. Calculate the remaining free space as for initial free space, above.
1204 // If the sum of the unfrozen flex items’ flex factors is less than one,
1205 // multiply the initial free space by this sum. If the magnitude of this
1206 // value is less than the magnitude of the remaining free space, use this
1207 // as the remaining free space.
1208
1209 let used_space: f32 = total_main_axis_gap
1210 + line
1211 .items
1212 .iter()
1213 .map(|child| {
1214 if child.frozen {
1215 child.outer_target_size.main(constants.dir)
1216 } else {
1217 child.flex_basis + child.margin.main_axis_sum(constants.dir)
1218 }
1219 })
1220 .sum::<f32>();
1221
1222 let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1223
1224 let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1225 unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1226 (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1227 });
1228
1229 let free_space = if growing && sum_flex_grow < 1.0 {
1230 (initial_free_space * sum_flex_grow - total_main_axis_gap)
1231 .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1232 } else if shrinking && sum_flex_shrink < 1.0 {
1233 (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1234 .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1235 } else {
1236 (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1237 .unwrap_or(used_flex_factor - used_space)
1238 };
1239
1240 // c. Distribute free space proportional to the flex factors.
1241 // - If the remaining free space is zero
1242 // Do Nothing
1243 // - If using the flex grow factor
1244 // Find the ratio of the item’s flex grow factor to the sum of the
1245 // flex grow factors of all unfrozen items on the line. Set the item’s
1246 // target main size to its flex base size plus a fraction of the remaining
1247 // free space proportional to the ratio.
1248 // - If using the flex shrink factor
1249 // For every unfrozen item on the line, multiply its flex shrink factor by
1250 // its inner flex base size, and note this as its scaled flex shrink factor.
1251 // Find the ratio of the item’s scaled flex shrink factor to the sum of the
1252 // scaled flex shrink factors of all unfrozen items on the line. Set the item’s
1253 // target main size to its flex base size minus a fraction of the absolute value
1254 // of the remaining free space proportional to the ratio. Note this may result
1255 // in a negative inner main size; it will be corrected in the next step.
1256 // - Otherwise
1257 // Do Nothing
1258
1259 if free_space.is_normal() {
1260 if growing && sum_flex_grow > 0.0 {
1261 for child in &mut unfrozen {
1262 child
1263 .target_size
1264 .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1265 }
1266 } else if shrinking && sum_flex_shrink > 0.0 {
1267 let sum_scaled_shrink_factor: f32 =
1268 unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1269
1270 if sum_scaled_shrink_factor > 0.0 {
1271 for child in &mut unfrozen {
1272 let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1273 child.target_size.set_main(
1274 constants.dir,
1275 child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1276 )
1277 }
1278 }
1279 }
1280 }
1281
1282 // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
1283 // used min and max main sizes and floor its content-box size at zero. If the
1284 // item’s target main size was made smaller by this, it’s a max violation.
1285 // If the item’s target main size was made larger by this, it’s a min violation.
1286
1287 let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1288 let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
1289 let max_main = child.max_size.main(constants.dir);
1290 let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1291 child.violation = clamped - child.target_size.main(constants.dir);
1292 child.target_size.set_main(constants.dir, clamped);
1293 child.outer_target_size.set_main(
1294 constants.dir,
1295 child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1296 );
1297
1298 acc + child.violation
1299 });
1300
1301 // e. Freeze over-flexed items. The total violation is the sum of the adjustments
1302 // from the previous step ∑(clamped size - unclamped size). If the total violation is:
1303 // - Zero
1304 // Freeze all items.
1305 // - Positive
1306 // Freeze all the items with min violations.
1307 // - Negative
1308 // Freeze all the items with max violations.
1309
1310 for child in &mut unfrozen {
1311 match total_violation {
1312 v if v > 0.0 => child.frozen = child.violation > 0.0,
1313 v if v < 0.0 => child.frozen = child.violation < 0.0,
1314 _ => child.frozen = true,
1315 }
1316 }
1317
1318 // f. Return to the start of this loop.
1319 }
1320}
1321
1322/// Determine the hypothetical cross size of each item.
1323///
1324/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1325///
1326/// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
1327/// by performing layout with the used main size and the available space, treating auto as fit-content.
1328#[inline]
1329fn determine_hypothetical_cross_size(
1330 tree: &mut impl LayoutFlexboxContainer,
1331 line: &mut FlexLine,
1332 constants: &AlgoConstants,
1333 available_space: Size<AvailableSpace>,
1334) {
1335 for child in line.items.iter_mut() {
1336 let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
1337
1338 let child_known_main = constants.container_size.main(constants.dir).into();
1339
1340 let child_cross = child
1341 .size
1342 .cross(constants.dir)
1343 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1344 .maybe_max(padding_border_sum);
1345
1346 let child_available_cross = available_space
1347 .cross(constants.dir)
1348 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1349 .maybe_max(padding_border_sum);
1350
1351 let child_inner_cross = child_cross.unwrap_or_else(|| {
1352 tree.measure_child_size(
1353 child.node,
1354 Size {
1355 width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1356 height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1357 },
1358 constants.node_inner_size,
1359 Size {
1360 width: if constants.is_row { child_known_main } else { child_available_cross },
1361 height: if constants.is_row { child_available_cross } else { child_known_main },
1362 },
1363 SizingMode::ContentSize,
1364 constants.dir.cross_axis(),
1365 Line::FALSE,
1366 )
1367 .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1368 .max(padding_border_sum)
1369 });
1370 let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
1371
1372 child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
1373 child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
1374 }
1375}
1376
1377/// Calculate the base lines of the children.
1378#[inline]
1379fn calculate_children_base_lines(
1380 tree: &mut impl LayoutFlexboxContainer,
1381 node_size: Size<Option<f32>>,
1382 available_space: Size<AvailableSpace>,
1383 flex_lines: &mut [FlexLine],
1384 constants: &AlgoConstants,
1385) {
1386 // Only compute baselines for flex rows because we only support baseline alignment in the cross axis
1387 // where that axis is also the inline axis
1388 // TODO: this may need revisiting if/when we support vertical writing modes
1389 if !constants.is_row {
1390 return;
1391 }
1392
1393 for line in flex_lines {
1394 // If a flex line has one or zero items participating in baseline alignment then baseline alignment is a no-op so we skip
1395 let line_baseline_child_count =
1396 line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1397 if line_baseline_child_count <= 1 {
1398 continue;
1399 }
1400
1401 for child in line.items.iter_mut() {
1402 // Only calculate baselines for children participating in baseline alignment
1403 if child.align_self != AlignSelf::Baseline {
1404 continue;
1405 }
1406
1407 let measured_size_and_baselines = tree.perform_child_layout(
1408 child.node,
1409 Size {
1410 width: if constants.is_row {
1411 child.target_size.width.into()
1412 } else {
1413 child.hypothetical_inner_size.width.into()
1414 },
1415 height: if constants.is_row {
1416 child.hypothetical_inner_size.height.into()
1417 } else {
1418 child.target_size.height.into()
1419 },
1420 },
1421 constants.node_inner_size,
1422 Size {
1423 width: if constants.is_row {
1424 constants.container_size.width.into()
1425 } else {
1426 available_space.width.maybe_set(node_size.width)
1427 },
1428 height: if constants.is_row {
1429 available_space.height.maybe_set(node_size.height)
1430 } else {
1431 constants.container_size.height.into()
1432 },
1433 },
1434 SizingMode::ContentSize,
1435 Line::FALSE,
1436 );
1437
1438 let baseline = measured_size_and_baselines.first_baselines.y;
1439 let height = measured_size_and_baselines.size.height;
1440
1441 child.baseline = baseline.unwrap_or(height) + child.margin.top;
1442 }
1443 }
1444}
1445
1446/// Calculate the cross size of each flex line.
1447///
1448/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1449///
1450/// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
1451#[inline]
1452fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1453 // If the flex container is single-line and has a definite cross size,
1454 // the cross size of the flex line is the flex container’s inner cross size.
1455 if !constants.is_wrap && node_size.cross(constants.dir).is_some() {
1456 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1457 let cross_min_size = constants.min_size.cross(constants.dir);
1458 let cross_max_size = constants.max_size.cross(constants.dir);
1459 flex_lines[0].cross_size = node_size
1460 .cross(constants.dir)
1461 .maybe_clamp(cross_min_size, cross_max_size)
1462 .maybe_sub(cross_axis_padding_border)
1463 .maybe_max(0.0)
1464 .unwrap_or(0.0);
1465 } else {
1466 // Otherwise, for each flex line:
1467 //
1468 // 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
1469 // align-self is baseline, and whose cross-axis margins are both non-auto. Find the
1470 // largest of the distances between each item’s baseline and its hypothetical outer
1471 // cross-start edge, and the largest of the distances between each item’s baseline
1472 // and its hypothetical outer cross-end edge, and sum these two values.
1473
1474 // 2. Among all the items not collected by the previous step, find the largest
1475 // outer hypothetical cross size.
1476
1477 // 3. The used cross-size of the flex line is the largest of the numbers found in the
1478 // previous two steps and zero.
1479 for line in flex_lines.iter_mut() {
1480 let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1481 line.cross_size = line
1482 .items
1483 .iter()
1484 .map(|child| {
1485 if child.align_self == AlignSelf::Baseline
1486 && !child.margin_is_auto.cross_start(constants.dir)
1487 && !child.margin_is_auto.cross_end(constants.dir)
1488 {
1489 max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1490 } else {
1491 child.hypothetical_outer_size.cross(constants.dir)
1492 }
1493 })
1494 .fold(0.0, |acc, x| acc.max(x));
1495 }
1496
1497 // If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes.
1498 // Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically.
1499 if !constants.is_wrap {
1500 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1501 let cross_min_size = constants.min_size.cross(constants.dir);
1502 let cross_max_size = constants.max_size.cross(constants.dir);
1503 flex_lines[0].cross_size = flex_lines[0].cross_size.maybe_clamp(
1504 cross_min_size.maybe_sub(cross_axis_padding_border),
1505 cross_max_size.maybe_sub(cross_axis_padding_border),
1506 );
1507 }
1508 }
1509}
1510
1511/// Handle 'align-content: stretch'.
1512///
1513/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1514///
1515/// - [**Handle 'align-content: stretch'**](https://www.w3.org/TR/css-flexbox-1/#algo-line-stretch). If the flex container has a definite cross size, align-content is stretch,
1516/// and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
1517/// increase the cross size of each flex line by equal amounts such that the sum of their cross sizes exactly equals the flex container’s inner cross size.
1518#[inline]
1519fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1520 if constants.align_content == AlignContent::Stretch {
1521 let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1522 let cross_min_size = constants.min_size.cross(constants.dir);
1523 let cross_max_size = constants.max_size.cross(constants.dir);
1524 let container_min_inner_cross = node_size
1525 .cross(constants.dir)
1526 .or(cross_min_size)
1527 .maybe_clamp(cross_min_size, cross_max_size)
1528 .maybe_sub(cross_axis_padding_border)
1529 .maybe_max(0.0)
1530 .unwrap_or(0.0);
1531
1532 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1533 let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1534
1535 if lines_total_cross < container_min_inner_cross {
1536 let remaining = container_min_inner_cross - lines_total_cross;
1537 let addition = remaining / flex_lines.len() as f32;
1538 flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1539 }
1540 }
1541}
1542
1543/// Determine the used cross size of each flex item.
1544///
1545/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1546///
1547/// - [**Determine the used cross size of each flex item**](https://www.w3.org/TR/css-flexbox-1/#algo-stretch). If a flex item has align-self: stretch, its computed cross size property is auto,
1548/// and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line, clamped according to the item’s used min and max cross sizes.
1549/// Otherwise, the used cross size is the item’s hypothetical cross size.
1550///
1551/// If the flex item has align-self: stretch, redo layout for its contents, treating this used size as its definite cross size so that percentage-sized children can be resolved.
1552///
1553/// **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
1554#[inline]
1555fn determine_used_cross_size(
1556 tree: &impl LayoutFlexboxContainer,
1557 flex_lines: &mut [FlexLine],
1558 constants: &AlgoConstants,
1559) {
1560 for line in flex_lines {
1561 let line_cross_size = line.cross_size;
1562
1563 for child in line.items.iter_mut() {
1564 let child_style = tree.get_flexbox_child_style(child.node);
1565 child.target_size.set_cross(
1566 constants.dir,
1567 if child.align_self == AlignSelf::Stretch
1568 && !child.margin_is_auto.cross_start(constants.dir)
1569 && !child.margin_is_auto.cross_end(constants.dir)
1570 && child_style.size().cross(constants.dir) == Dimension::Auto
1571 {
1572 // For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
1573 // using the aspect_ratio (if set). Both Chrome and Firefox agree on this. And reading the spec, it seems like
1574 // a reasonable interpretation. Although it seems to me that the spec *should* apply aspect_ratio here.
1575 let padding = child_style.padding().resolve_or_zero(constants.node_inner_size);
1576 let border = child_style.border().resolve_or_zero(constants.node_inner_size);
1577 let pb_sum = (padding + border).sum_axes();
1578 let box_sizing_adjustment =
1579 if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
1580
1581 let max_size_ignoring_aspect_ratio = child_style
1582 .max_size()
1583 .maybe_resolve(constants.node_inner_size)
1584 .maybe_add(box_sizing_adjustment);
1585
1586 (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1587 child.min_size.cross(constants.dir),
1588 max_size_ignoring_aspect_ratio.cross(constants.dir),
1589 )
1590 } else {
1591 child.hypothetical_inner_size.cross(constants.dir)
1592 },
1593 );
1594
1595 child.outer_target_size.set_cross(
1596 constants.dir,
1597 child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1598 );
1599 }
1600 }
1601}
1602
1603/// Distribute any remaining free space.
1604///
1605/// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1606///
1607/// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1608///
1609/// 1. If the remaining free space is positive and at least one main-axis margin on this line is `auto`, distribute the free space equally among these margins.
1610/// Otherwise, set all `auto` margins to zero.
1611///
1612/// 2. Align the items along the main-axis per `justify-content`.
1613#[inline]
1614fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1615 for line in flex_lines {
1616 let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1617 let used_space: f32 = total_main_axis_gap
1618 + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1619 let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1620 let mut num_auto_margins = 0;
1621
1622 for child in line.items.iter_mut() {
1623 if child.margin_is_auto.main_start(constants.dir) {
1624 num_auto_margins += 1;
1625 }
1626 if child.margin_is_auto.main_end(constants.dir) {
1627 num_auto_margins += 1;
1628 }
1629 }
1630
1631 if free_space > 0.0 && num_auto_margins > 0 {
1632 let margin = free_space / num_auto_margins as f32;
1633
1634 for child in line.items.iter_mut() {
1635 if child.margin_is_auto.main_start(constants.dir) {
1636 if constants.is_row {
1637 child.margin.left = margin;
1638 } else {
1639 child.margin.top = margin;
1640 }
1641 }
1642 if child.margin_is_auto.main_end(constants.dir) {
1643 if constants.is_row {
1644 child.margin.right = margin;
1645 } else {
1646 child.margin.bottom = margin;
1647 }
1648 }
1649 }
1650 } else {
1651 let num_items = line.items.len();
1652 let layout_reverse = constants.dir.is_reverse();
1653 let gap = constants.gap.main(constants.dir);
1654 let is_safe = false; // TODO: Implement safe alignment
1655 let raw_justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1656 let justify_content_mode =
1657 apply_alignment_fallback(free_space, num_items, raw_justify_content_mode, is_safe);
1658
1659 let justify_item = |(i, child): (usize, &mut FlexItem)| {
1660 child.offset_main =
1661 compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1662 };
1663
1664 if layout_reverse {
1665 line.items.iter_mut().rev().enumerate().for_each(justify_item);
1666 } else {
1667 line.items.iter_mut().enumerate().for_each(justify_item);
1668 }
1669 }
1670 }
1671}
1672
1673/// Resolve cross-axis `auto` margins.
1674///
1675/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1676///
1677/// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1678/// If a flex item has auto cross-axis margins:
1679///
1680/// - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1681/// distribute the difference in those sizes equally to the auto margins.
1682///
1683/// - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1684/// Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1685#[inline]
1686fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1687 for line in flex_lines {
1688 let line_cross_size = line.cross_size;
1689 let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1690
1691 for child in line.items.iter_mut() {
1692 let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1693
1694 if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1695 if constants.is_row {
1696 child.margin.top = free_space / 2.0;
1697 child.margin.bottom = free_space / 2.0;
1698 } else {
1699 child.margin.left = free_space / 2.0;
1700 child.margin.right = free_space / 2.0;
1701 }
1702 } else if child.margin_is_auto.cross_start(constants.dir) {
1703 if constants.is_row {
1704 child.margin.top = free_space;
1705 } else {
1706 child.margin.left = free_space;
1707 }
1708 } else if child.margin_is_auto.cross_end(constants.dir) {
1709 if constants.is_row {
1710 child.margin.bottom = free_space;
1711 } else {
1712 child.margin.right = free_space;
1713 }
1714 } else {
1715 // 14. Align all flex items along the cross-axis.
1716 child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1717 }
1718 }
1719 }
1720}
1721
1722/// Align all flex items along the cross-axis.
1723///
1724/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1725///
1726/// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1727/// if neither of the item's cross-axis margins are `auto`.
1728#[inline]
1729fn align_flex_items_along_cross_axis(
1730 child: &FlexItem,
1731 free_space: f32,
1732 max_baseline: f32,
1733 constants: &AlgoConstants,
1734) -> f32 {
1735 match child.align_self {
1736 AlignSelf::Start => 0.0,
1737 AlignSelf::FlexStart => {
1738 if constants.is_wrap_reverse {
1739 free_space
1740 } else {
1741 0.0
1742 }
1743 }
1744 AlignSelf::End => free_space,
1745 AlignSelf::FlexEnd => {
1746 if constants.is_wrap_reverse {
1747 0.0
1748 } else {
1749 free_space
1750 }
1751 }
1752 AlignSelf::Center => free_space / 2.0,
1753 AlignSelf::Baseline => {
1754 if constants.is_row {
1755 max_baseline - child.baseline
1756 } else {
1757 // Until we support vertical writing modes, baseline alignment only makes sense if
1758 // the constants.direction is row, so we treat it as flex-start alignment in columns.
1759 if constants.is_wrap_reverse {
1760 free_space
1761 } else {
1762 0.0
1763 }
1764 }
1765 }
1766 AlignSelf::Stretch => {
1767 if constants.is_wrap_reverse {
1768 free_space
1769 } else {
1770 0.0
1771 }
1772 }
1773 }
1774}
1775
1776/// Determine the flex container’s used cross size.
1777///
1778/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1779///
1780/// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1781///
1782/// - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1783///
1784/// - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1785#[inline]
1786#[must_use]
1787fn determine_container_cross_size(
1788 flex_lines: &[FlexLine],
1789 node_size: Size<Option<f32>>,
1790 constants: &mut AlgoConstants,
1791) -> f32 {
1792 let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1793 let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1794
1795 let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
1796 let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
1797 let min_cross_size = constants.min_size.cross(constants.dir);
1798 let max_cross_size = constants.max_size.cross(constants.dir);
1799 let outer_container_size = node_size
1800 .cross(constants.dir)
1801 .unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
1802 .maybe_clamp(min_cross_size, max_cross_size)
1803 .max(padding_border_sum - cross_scrollbar_gutter);
1804 let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
1805
1806 constants.container_size.set_cross(constants.dir, outer_container_size);
1807 constants.inner_container_size.set_cross(constants.dir, inner_container_size);
1808
1809 total_line_cross_size
1810}
1811
1812/// Align all flex lines per `align-content`.
1813///
1814/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1815///
1816/// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1817#[inline]
1818fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1819 let num_lines = flex_lines.len();
1820 let gap = constants.gap.cross(constants.dir);
1821 let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1822 let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1823 let is_safe = false; // TODO: Implement safe alignment
1824
1825 let align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content, is_safe);
1826
1827 let align_line = |(i, line): (usize, &mut FlexLine)| {
1828 line.offset_cross =
1829 compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1830 };
1831
1832 if constants.is_wrap_reverse {
1833 flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1834 } else {
1835 flex_lines.iter_mut().enumerate().for_each(align_line);
1836 }
1837}
1838
1839/// Calculates the layout for a flex-item
1840#[allow(clippy::too_many_arguments)]
1841fn calculate_flex_item(
1842 tree: &mut impl LayoutFlexboxContainer,
1843 item: &mut FlexItem,
1844 total_offset_main: &mut f32,
1845 total_offset_cross: f32,
1846 line_offset_cross: f32,
1847 #[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
1848 container_size: Size<f32>,
1849 node_inner_size: Size<Option<f32>>,
1850 direction: FlexDirection,
1851) {
1852 let layout_output = tree.perform_child_layout(
1853 item.node,
1854 item.target_size.map(|s| s.into()),
1855 node_inner_size,
1856 container_size.map(|s| s.into()),
1857 SizingMode::ContentSize,
1858 Line::FALSE,
1859 );
1860 let LayoutOutput {
1861 size,
1862 #[cfg(feature = "content_size")]
1863 content_size,
1864 ..
1865 } = layout_output;
1866
1867 let offset_main = *total_offset_main
1868 + item.offset_main
1869 + item.margin.main_start(direction)
1870 + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1871
1872 let offset_cross = total_offset_cross
1873 + item.offset_cross
1874 + line_offset_cross
1875 + item.margin.cross_start(direction)
1876 + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1877
1878 if direction.is_row() {
1879 let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1880 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1881 item.baseline = baseline_offset_cross + inner_baseline;
1882 } else {
1883 let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1884 let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1885 item.baseline = baseline_offset_main + inner_baseline;
1886 }
1887
1888 let location = match direction.is_row() {
1889 true => Point { x: offset_main, y: offset_cross },
1890 false => Point { x: offset_cross, y: offset_main },
1891 };
1892 let scrollbar_size = Size {
1893 width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1894 height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1895 };
1896
1897 tree.set_unrounded_layout(
1898 item.node,
1899 &Layout {
1900 order: item.order,
1901 size,
1902 #[cfg(feature = "content_size")]
1903 content_size,
1904 scrollbar_size,
1905 location,
1906 padding: item.padding,
1907 border: item.border,
1908 margin: item.margin,
1909 },
1910 );
1911
1912 *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
1913
1914 #[cfg(feature = "content_size")]
1915 {
1916 *total_content_size =
1917 total_content_size.f32_max(compute_content_size_contribution(location, size, content_size, item.overflow));
1918 }
1919}
1920
1921/// Calculates the layout line
1922#[allow(clippy::too_many_arguments)]
1923fn calculate_layout_line(
1924 tree: &mut impl LayoutFlexboxContainer,
1925 line: &mut FlexLine,
1926 total_offset_cross: &mut f32,
1927 #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
1928 container_size: Size<f32>,
1929 node_inner_size: Size<Option<f32>>,
1930 padding_border: Rect<f32>,
1931 direction: FlexDirection,
1932) {
1933 let mut total_offset_main = padding_border.main_start(direction);
1934 let line_offset_cross = line.offset_cross;
1935
1936 if direction.is_reverse() {
1937 for item in line.items.iter_mut().rev() {
1938 calculate_flex_item(
1939 tree,
1940 item,
1941 &mut total_offset_main,
1942 *total_offset_cross,
1943 line_offset_cross,
1944 #[cfg(feature = "content_size")]
1945 content_size,
1946 container_size,
1947 node_inner_size,
1948 direction,
1949 );
1950 }
1951 } else {
1952 for item in line.items.iter_mut() {
1953 calculate_flex_item(
1954 tree,
1955 item,
1956 &mut total_offset_main,
1957 *total_offset_cross,
1958 line_offset_cross,
1959 #[cfg(feature = "content_size")]
1960 content_size,
1961 container_size,
1962 node_inner_size,
1963 direction,
1964 );
1965 }
1966 }
1967
1968 *total_offset_cross += line_offset_cross + line.cross_size;
1969}
1970
1971/// Do a final layout pass and collect the resulting layouts.
1972#[inline]
1973fn final_layout_pass(
1974 tree: &mut impl LayoutFlexboxContainer,
1975 flex_lines: &mut [FlexLine],
1976 constants: &AlgoConstants,
1977) -> Size<f32> {
1978 let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
1979
1980 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
1981 let mut content_size = Size::ZERO;
1982
1983 if constants.is_wrap_reverse {
1984 for line in flex_lines.iter_mut().rev() {
1985 calculate_layout_line(
1986 tree,
1987 line,
1988 &mut total_offset_cross,
1989 #[cfg(feature = "content_size")]
1990 &mut content_size,
1991 constants.container_size,
1992 constants.node_inner_size,
1993 constants.content_box_inset,
1994 constants.dir,
1995 );
1996 }
1997 } else {
1998 for line in flex_lines.iter_mut() {
1999 calculate_layout_line(
2000 tree,
2001 line,
2002 &mut total_offset_cross,
2003 #[cfg(feature = "content_size")]
2004 &mut content_size,
2005 constants.container_size,
2006 constants.node_inner_size,
2007 constants.content_box_inset,
2008 constants.dir,
2009 );
2010 }
2011 }
2012
2013 content_size.width += constants.content_box_inset.right - constants.border.right - constants.scrollbar_gutter.x;
2014 content_size.height += constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
2015
2016 content_size
2017}
2018
2019/// Perform absolute layout on all absolutely positioned children.
2020#[inline]
2021fn perform_absolute_layout_on_absolute_children(
2022 tree: &mut impl LayoutFlexboxContainer,
2023 node: NodeId,
2024 constants: &AlgoConstants,
2025) -> Size<f32> {
2026 let container_width = constants.container_size.width;
2027 let container_height = constants.container_size.height;
2028 let inset_relative_size =
2029 constants.container_size - constants.border.sum_axes() - constants.scrollbar_gutter.into();
2030
2031 #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
2032 let mut content_size = Size::ZERO;
2033
2034 for order in 0..tree.child_count(node) {
2035 let child = tree.get_child_id(node, order);
2036 let child_style = tree.get_flexbox_child_style(child);
2037
2038 // Skip items that are display:none or are not position:absolute
2039 if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
2040 {
2041 continue;
2042 }
2043
2044 let overflow = child_style.overflow();
2045 let scrollbar_width = child_style.scrollbar_width();
2046 let aspect_ratio = child_style.aspect_ratio();
2047 let align_self = child_style.align_self().unwrap_or(constants.align_items);
2048 let margin = child_style.margin().map(|margin| margin.resolve_to_option(inset_relative_size.width));
2049 let padding = child_style.padding().resolve_or_zero(Some(inset_relative_size.width));
2050 let border = child_style.border().resolve_or_zero(Some(inset_relative_size.width));
2051 let padding_border_sum = (padding + border).sum_axes();
2052 let box_sizing_adjustment =
2053 if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
2054
2055 // Resolve inset
2056 // Insets are resolved against the container size minus border
2057 let left = child_style.inset().left.maybe_resolve(inset_relative_size.width);
2058 let right = child_style.inset().right.maybe_resolve(inset_relative_size.width);
2059 let top = child_style.inset().top.maybe_resolve(inset_relative_size.height);
2060 let bottom = child_style.inset().bottom.maybe_resolve(inset_relative_size.height);
2061
2062 // Compute known dimensions from min/max/inherent size styles
2063 let style_size = child_style
2064 .size()
2065 .maybe_resolve(inset_relative_size)
2066 .maybe_apply_aspect_ratio(aspect_ratio)
2067 .maybe_add(box_sizing_adjustment);
2068 let min_size = child_style
2069 .min_size()
2070 .maybe_resolve(inset_relative_size)
2071 .maybe_apply_aspect_ratio(aspect_ratio)
2072 .maybe_add(box_sizing_adjustment)
2073 .or(padding_border_sum.map(Some))
2074 .maybe_max(padding_border_sum);
2075 let max_size = child_style
2076 .max_size()
2077 .maybe_resolve(inset_relative_size)
2078 .maybe_apply_aspect_ratio(aspect_ratio)
2079 .maybe_add(box_sizing_adjustment);
2080 let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
2081
2082 drop(child_style);
2083
2084 // Fill in width from left/right and reapply aspect ratio if:
2085 // - Width is not already known
2086 // - Item has both left and right inset properties set
2087 if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
2088 let new_width_raw = inset_relative_size.width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
2089 known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
2090 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2091 }
2092
2093 // Fill in height from top/bottom and reapply aspect ratio if:
2094 // - Height is not already known
2095 // - Item has both top and bottom inset properties set
2096 if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
2097 let new_height_raw =
2098 inset_relative_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
2099 known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
2100 known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2101 }
2102 let layout_output = tree.perform_child_layout(
2103 child,
2104 known_dimensions,
2105 constants.node_inner_size,
2106 Size {
2107 width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
2108 height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
2109 },
2110 SizingMode::InherentSize,
2111 Line::FALSE,
2112 );
2113 let measured_size = layout_output.size;
2114 let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
2115
2116 let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
2117
2118 let free_space = Size {
2119 width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
2120 height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
2121 }
2122 .f32_max(Size::ZERO);
2123
2124 // Expand auto margins to fill available space
2125 let resolved_margin = {
2126 let auto_margin_size = Size {
2127 width: {
2128 let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
2129 if auto_margin_count > 0 {
2130 free_space.width / auto_margin_count as f32
2131 } else {
2132 0.0
2133 }
2134 },
2135 height: {
2136 let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
2137 if auto_margin_count > 0 {
2138 free_space.height / auto_margin_count as f32
2139 } else {
2140 0.0
2141 }
2142 },
2143 };
2144
2145 Rect {
2146 left: margin.left.unwrap_or(auto_margin_size.width),
2147 right: margin.right.unwrap_or(auto_margin_size.width),
2148 top: margin.top.unwrap_or(auto_margin_size.height),
2149 bottom: margin.bottom.unwrap_or(auto_margin_size.height),
2150 }
2151 };
2152
2153 // Determine flex-relative insets
2154 let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
2155 let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
2156
2157 // Apply main-axis alignment
2158 // let free_main_space = free_space.main(constants.dir) - resolved_margin.main_axis_sum(constants.dir);
2159 let offset_main = if let Some(start) = start_main {
2160 start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2161 } else if let Some(end) = end_main {
2162 constants.container_size.main(constants.dir)
2163 - constants.border.main_end(constants.dir)
2164 - constants.scrollbar_gutter.main(constants.dir)
2165 - final_size.main(constants.dir)
2166 - end
2167 - resolved_margin.main_end(constants.dir)
2168 } else {
2169 // Stretch is an invalid value for justify_content in the flexbox algorithm, so we
2170 // treat it as if it wasn't set (and thus we default to FlexStart behaviour)
2171 match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
2172 (JustifyContent::SpaceBetween, _)
2173 | (JustifyContent::Start, _)
2174 | (JustifyContent::Stretch, false)
2175 | (JustifyContent::FlexStart, false)
2176 | (JustifyContent::FlexEnd, true) => {
2177 constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2178 }
2179 (JustifyContent::End, _)
2180 | (JustifyContent::FlexEnd, false)
2181 | (JustifyContent::FlexStart, true)
2182 | (JustifyContent::Stretch, true) => {
2183 constants.container_size.main(constants.dir)
2184 - constants.content_box_inset.main_end(constants.dir)
2185 - final_size.main(constants.dir)
2186 - resolved_margin.main_end(constants.dir)
2187 }
2188 (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
2189 (constants.container_size.main(constants.dir)
2190 + constants.content_box_inset.main_start(constants.dir)
2191 - constants.content_box_inset.main_end(constants.dir)
2192 - final_size.main(constants.dir)
2193 + resolved_margin.main_start(constants.dir)
2194 - resolved_margin.main_end(constants.dir))
2195 / 2.0
2196 }
2197 }
2198 };
2199
2200 // Apply cross-axis alignment
2201 // let free_cross_space = free_space.cross(constants.dir) - resolved_margin.cross_axis_sum(constants.dir);
2202 let offset_cross = if let Some(start) = start_cross {
2203 start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2204 } else if let Some(end) = end_cross {
2205 constants.container_size.cross(constants.dir)
2206 - constants.border.cross_end(constants.dir)
2207 - constants.scrollbar_gutter.cross(constants.dir)
2208 - final_size.cross(constants.dir)
2209 - end
2210 - resolved_margin.cross_end(constants.dir)
2211 } else {
2212 match (align_self, constants.is_wrap_reverse) {
2213 // Stretch alignment does not apply to absolutely positioned items
2214 // See "Example 3" at https://www.w3.org/TR/css-flexbox-1/#abspos-items
2215 // Note: Stretch should be FlexStart not Start when we support both
2216 (AlignSelf::Start, _)
2217 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
2218 | (AlignSelf::FlexEnd, true) => {
2219 constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2220 }
2221 (AlignSelf::End, _)
2222 | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
2223 | (AlignSelf::FlexEnd, false) => {
2224 constants.container_size.cross(constants.dir)
2225 - constants.content_box_inset.cross_end(constants.dir)
2226 - final_size.cross(constants.dir)
2227 - resolved_margin.cross_end(constants.dir)
2228 }
2229 (AlignSelf::Center, _) => {
2230 (constants.container_size.cross(constants.dir)
2231 + constants.content_box_inset.cross_start(constants.dir)
2232 - constants.content_box_inset.cross_end(constants.dir)
2233 - final_size.cross(constants.dir)
2234 + resolved_margin.cross_start(constants.dir)
2235 - resolved_margin.cross_end(constants.dir))
2236 / 2.0
2237 }
2238 }
2239 };
2240
2241 let location = match constants.is_row {
2242 true => Point { x: offset_main, y: offset_cross },
2243 false => Point { x: offset_cross, y: offset_main },
2244 };
2245 let scrollbar_size = Size {
2246 width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
2247 height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
2248 };
2249 tree.set_unrounded_layout(
2250 child,
2251 &Layout {
2252 order: order as u32,
2253 size: final_size,
2254 #[cfg(feature = "content_size")]
2255 content_size: layout_output.content_size,
2256 scrollbar_size,
2257 location,
2258 padding,
2259 border,
2260 margin: resolved_margin,
2261 },
2262 );
2263
2264 #[cfg(feature = "content_size")]
2265 {
2266 let size_content_size_contribution = Size {
2267 width: match overflow.x {
2268 Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
2269 _ => final_size.width,
2270 },
2271 height: match overflow.y {
2272 Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
2273 _ => final_size.height,
2274 },
2275 };
2276 if size_content_size_contribution.has_non_zero_area() {
2277 let content_size_contribution = Size {
2278 width: location.x + size_content_size_contribution.width,
2279 height: location.y + size_content_size_contribution.height,
2280 };
2281 content_size = content_size.f32_max(content_size_contribution);
2282 }
2283 }
2284 }
2285
2286 content_size
2287}
2288
2289/// Computes the total space taken up by gaps in an axis given:
2290/// - The size of each gap
2291/// - The number of items (children or flex-lines) between which there are gaps
2292#[inline(always)]
2293fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2294 // Gaps only exist between items, so...
2295 if num_items <= 1 {
2296 // ...if there are less than 2 items then there are no gaps
2297 0.0
2298 } else {
2299 // ...otherwise there are (num_items - 1) gaps
2300 gap * (num_items - 1) as f32
2301 }
2302}