taffy/compute/
flexbox.rs

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
644        // Clamp available space by min- and max- size
645        let cross_axis_available_space: AvailableSpace = match available_space.cross(dir) {
646            AvailableSpace::Definite(val) => AvailableSpace::Definite(
647                cross_axis_parent_size.unwrap_or(val).maybe_clamp(child_min_cross, child_max_cross),
648            ),
649            AvailableSpace::MinContent => match child_min_cross {
650                Some(min) => AvailableSpace::Definite(min),
651                None => AvailableSpace::MinContent,
652            },
653            AvailableSpace::MaxContent => match child_max_cross {
654                Some(max) => AvailableSpace::Definite(max),
655                None => AvailableSpace::MaxContent,
656            },
657        };
658
659        // Known dimensions for child sizing
660        let child_known_dimensions = {
661            let mut ckd = child.size.with_main(dir, None);
662            if child.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
663                ckd.set_cross(
664                    dir,
665                    cross_axis_available_space.into_option().maybe_sub(child.margin.cross_axis_sum(dir)),
666                );
667            }
668            ckd
669        };
670
671        let container_width = constants.node_inner_size.main(dir);
672        let box_sizing_adjustment = if child_style.box_sizing() == BoxSizing::ContentBox {
673            let padding = child_style.padding().resolve_or_zero(container_width);
674            let border = child_style.border().resolve_or_zero(container_width);
675            (padding + border).sum_axes()
676        } else {
677            Size::ZERO
678        }
679        .main(dir);
680        let flex_basis = child_style.flex_basis().maybe_resolve(container_width).maybe_add(box_sizing_adjustment);
681
682        drop(child_style);
683
684        child.flex_basis = 'flex_basis: {
685            // A. If the item has a definite used flex basis, that’s the flex base size.
686
687            // B. If the flex item has an intrinsic aspect ratio,
688            //    a used flex basis of content, and a definite cross size,
689            //    then the flex base size is calculated from its inner
690            //    cross size and the flex item’s intrinsic aspect ratio.
691
692            // Note: `child.size` has already been resolved against aspect_ratio in generate_anonymous_flex_items
693            // So B will just work here by using main_size without special handling for aspect_ratio
694            let main_size = child.size.main(dir);
695            if let Some(flex_basis) = flex_basis.or(main_size) {
696                break 'flex_basis flex_basis;
697            };
698
699            // C. If the used flex basis is content or depends on its available space,
700            //    and the flex container is being sized under a min-content or max-content
701            //    constraint (e.g. when performing automatic table layout [CSS21]),
702            //    size the item under that constraint. The flex base size is the item’s
703            //    resulting main size.
704
705            // This is covered by the implementation of E below, which passes the available_space constraint
706            // through to the child size computation. It may need a separate implementation if/when D is implemented.
707
708            // D. Otherwise, if the used flex basis is content or depends on its
709            //    available space, the available main size is infinite, and the flex item’s
710            //    inline axis is parallel to the main axis, lay the item out using the rules
711            //    for a box in an orthogonal flow [CSS3-WRITING-MODES]. The flex base size
712            //    is the item’s max-content main size.
713
714            // TODO if/when vertical writing modes are supported
715
716            // E. Otherwise, size the item into the available space using its used flex basis
717            //    in place of its main size, treating a value of content as max-content.
718            //    If a cross size is needed to determine the main size (e.g. when the
719            //    flex item’s main size is in its block axis) and the flex item’s cross size
720            //    is auto and not definite, in this calculation use fit-content as the
721            //    flex item’s cross size. The flex base size is the item’s resulting main size.
722
723            let child_available_space = Size::MAX_CONTENT
724                .with_main(
725                    dir,
726                    // Map AvailableSpace::Definite to AvailableSpace::MaxContent
727                    if available_space.main(dir) == AvailableSpace::MinContent {
728                        AvailableSpace::MinContent
729                    } else {
730                        AvailableSpace::MaxContent
731                    },
732                )
733                .with_cross(dir, cross_axis_available_space);
734
735            debug_log!("COMPUTE CHILD BASE SIZE:");
736            break 'flex_basis tree.measure_child_size(
737                child.node,
738                child_known_dimensions,
739                child_parent_size,
740                child_available_space,
741                SizingMode::ContentSize,
742                dir.main_axis(),
743                Line::FALSE,
744            );
745        };
746
747        // Floor flex-basis by the padding_border_sum (floors inner_flex_basis at zero)
748        // This seems to be in violation of the spec which explicitly states that the content box should not be floored at zero
749        // (like it usually is) when calculating the flex-basis. But including this matches both Chrome and Firefox's behaviour.
750        //
751        // TODO: resolve spec violation
752        // Spec: https://www.w3.org/TR/css-flexbox-1/#intrinsic-item-contributions
753        // Spec: https://www.w3.org/TR/css-flexbox-1/#change-2016-max-contribution
754        let padding_border_sum = child.padding.main_axis_sum(constants.dir) + child.border.main_axis_sum(constants.dir);
755        child.flex_basis = child.flex_basis.max(padding_border_sum);
756
757        // The hypothetical main size is the item’s flex base size clamped according to its
758        // used min and max main sizes (and flooring the content box size at zero).
759
760        child.inner_flex_basis =
761            child.flex_basis - child.padding.main_axis_sum(constants.dir) - child.border.main_axis_sum(constants.dir);
762
763        let padding_border_axes_sums = (child.padding + child.border).sum_axes().map(Some);
764
765        // Note that it is important that the `parent_size` parameter in the main axis is not set for this
766        // function call as it used for resolving percentages, and percentage size in an axis should not contribute
767        // to a min-content contribution in that same axis. However the `parent_size` and `available_space` *should*
768        // be set to their usual values in the cross axis so that wrapping content can wrap correctly.
769        //
770        // See https://drafts.csswg.org/css-sizing-3/#min-percentage-contribution
771        let style_min_main_size =
772            child.min_size.or(child.overflow.map(Overflow::maybe_into_automatic_min_size).into()).main(dir);
773
774        child.resolved_minimum_main_size = style_min_main_size.unwrap_or({
775            let min_content_main_size = {
776                let child_available_space = Size::MIN_CONTENT.with_cross(dir, cross_axis_available_space);
777
778                debug_log!("COMPUTE CHILD MIN SIZE:");
779                tree.measure_child_size(
780                    child.node,
781                    child_known_dimensions,
782                    child_parent_size,
783                    child_available_space,
784                    SizingMode::ContentSize,
785                    dir.main_axis(),
786                    Line::FALSE,
787                )
788            };
789
790            // 4.5. Automatic Minimum Size of Flex Items
791            // https://www.w3.org/TR/css-flexbox-1/#min-size-auto
792            let clamped_min_content_size =
793                min_content_main_size.maybe_min(child.size.main(dir)).maybe_min(child.max_size.main(dir));
794            clamped_min_content_size.maybe_max(padding_border_axes_sums.main(dir))
795        });
796
797        let hypothetical_inner_min_main =
798            child.resolved_minimum_main_size.maybe_max(padding_border_axes_sums.main(constants.dir));
799        let hypothetical_inner_size =
800            child.flex_basis.maybe_clamp(Some(hypothetical_inner_min_main), child.max_size.main(constants.dir));
801        let hypothetical_outer_size = hypothetical_inner_size + child.margin.main_axis_sum(constants.dir);
802
803        child.hypothetical_inner_size.set_main(constants.dir, hypothetical_inner_size);
804        child.hypothetical_outer_size.set_main(constants.dir, hypothetical_outer_size);
805    }
806}
807
808/// Collect flex items into flex lines.
809///
810/// # [9.3. Main Size Determination](https://www.w3.org/TR/css-flexbox-1/#main-sizing)
811///
812/// - [**Collect flex items into flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-break):
813///
814///     - If the flex container is single-line, collect all the flex items into a single flex line.
815///
816///     - 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
817///         (or until a forced break is encountered, see [§10 Fragmenting Flex Layout](https://www.w3.org/TR/css-flexbox-1/#pagination)).
818///         If the very first uncollected item wouldn't fit, collect just it into the line.
819///
820///         For this step, the size of a flex item is its outer hypothetical main size. (**Note: This can be negative**.)
821///
822///         Repeat until all flex items have been collected into flex lines.
823///
824///         **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**.
825#[inline]
826fn collect_flex_lines<'a>(
827    constants: &AlgoConstants,
828    available_space: Size<AvailableSpace>,
829    flex_items: &'a mut Vec<FlexItem>,
830) -> Vec<FlexLine<'a>> {
831    if !constants.is_wrap {
832        let mut lines = new_vec_with_capacity(1);
833        lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
834        lines
835    } else {
836        let main_axis_available_space = match constants.max_size.main(constants.dir) {
837            Some(max_size) => AvailableSpace::Definite(
838                available_space
839                    .main(constants.dir)
840                    .into_option()
841                    .unwrap_or(max_size)
842                    .maybe_max(constants.min_size.main(constants.dir)),
843            ),
844            None => available_space.main(constants.dir),
845        };
846
847        match main_axis_available_space {
848            // If we're sizing under a max-content constraint then the flex items will never wrap
849            // (at least for now - future extensions to the CSS spec may add provisions for forced wrap points)
850            AvailableSpace::MaxContent => {
851                let mut lines = new_vec_with_capacity(1);
852                lines.push(FlexLine { items: flex_items.as_mut_slice(), cross_size: 0.0, offset_cross: 0.0 });
853                lines
854            }
855            // If flex-wrap is Wrap and we're sizing under a min-content constraint, then we take every possible wrapping opportunity
856            // and place each item in it's own line
857            AvailableSpace::MinContent => {
858                let mut lines = new_vec_with_capacity(flex_items.len());
859                let mut items = &mut flex_items[..];
860                while !items.is_empty() {
861                    let (line_items, rest) = items.split_at_mut(1);
862                    lines.push(FlexLine { items: line_items, cross_size: 0.0, offset_cross: 0.0 });
863                    items = rest;
864                }
865                lines
866            }
867            AvailableSpace::Definite(main_axis_available_space) => {
868                let mut lines = new_vec_with_capacity(1);
869                let mut flex_items = &mut flex_items[..];
870                let main_axis_gap = constants.gap.main(constants.dir);
871
872                while !flex_items.is_empty() {
873                    // Find index of the first item in the next line
874                    // (or the last item if all remaining items are in the current line)
875                    let mut line_length = 0.0;
876                    let index = flex_items
877                        .iter()
878                        .enumerate()
879                        .find(|&(idx, child)| {
880                            // Gaps only occur between items (not before the first one or after the last one)
881                            // So first item in the line does not contribute a gap to the line length
882                            let gap_contribution = if idx == 0 { 0.0 } else { main_axis_gap };
883                            line_length += child.hypothetical_outer_size.main(constants.dir) + gap_contribution;
884                            line_length > main_axis_available_space && idx != 0
885                        })
886                        .map(|(idx, _)| idx)
887                        .unwrap_or(flex_items.len());
888
889                    let (items, rest) = flex_items.split_at_mut(index);
890                    lines.push(FlexLine { items, cross_size: 0.0, offset_cross: 0.0 });
891                    flex_items = rest;
892                }
893                lines
894            }
895        }
896    }
897}
898
899/// Determine the container's main size (if not already known)
900fn determine_container_main_size(
901    tree: &mut impl LayoutFlexboxContainer,
902    available_space: Size<AvailableSpace>,
903    lines: &mut [FlexLine<'_>],
904    constants: &mut AlgoConstants,
905) {
906    let dir = constants.dir;
907    let main_content_box_inset = constants.content_box_inset.main_axis_sum(constants.dir);
908
909    let outer_main_size: f32 = constants.node_outer_size.main(constants.dir).unwrap_or_else(|| {
910        match available_space.main(dir) {
911            AvailableSpace::Definite(main_axis_available_space) => {
912                let longest_line_length: f32 = lines
913                    .iter()
914                    .map(|line| {
915                        let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
916                        let total_target_size = line
917                            .items
918                            .iter()
919                            .map(|child| {
920                                let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
921                                (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
922                                    + child.margin.main_axis_sum(constants.dir))
923                                .max(padding_border_sum)
924                            })
925                            .sum::<f32>();
926                        total_target_size + line_main_axis_gap
927                    })
928                    .max_by(|a, b| a.total_cmp(b))
929                    .unwrap_or(0.0);
930                let size = longest_line_length + main_content_box_inset;
931                if lines.len() > 1 {
932                    f32_max(size, main_axis_available_space)
933                } else {
934                    size
935                }
936            }
937            AvailableSpace::MinContent if constants.is_wrap => {
938                let longest_line_length: f32 = lines
939                    .iter()
940                    .map(|line| {
941                        let line_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
942                        let total_target_size = line
943                            .items
944                            .iter()
945                            .map(|child| {
946                                let padding_border_sum = (child.padding + child.border).main_axis_sum(constants.dir);
947                                (child.flex_basis.maybe_max(child.min_size.main(constants.dir))
948                                    + child.margin.main_axis_sum(constants.dir))
949                                .max(padding_border_sum)
950                            })
951                            .sum::<f32>();
952                        total_target_size + line_main_axis_gap
953                    })
954                    .max_by(|a, b| a.total_cmp(b))
955                    .unwrap_or(0.0);
956                longest_line_length + main_content_box_inset
957            }
958            AvailableSpace::MinContent | AvailableSpace::MaxContent => {
959                // Define a base main_size variable. This is mutated once for iteration over the outer
960                // loop over the flex lines as:
961                //   "The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line."
962                let mut main_size = 0.0;
963
964                for line in lines.iter_mut() {
965                    for item in line.items.iter_mut() {
966                        let style_min = item.min_size.main(constants.dir);
967                        let style_preferred = item.size.main(constants.dir);
968                        let style_max = item.max_size.main(constants.dir);
969
970                        // The spec seems a bit unclear on this point (my initial reading was that the `.maybe_max(style_preferred)` should
971                        // not be included here), however this matches both Chrome and Firefox as of 9th March 2023.
972                        //
973                        // Spec: https://www.w3.org/TR/css-flexbox-1/#intrinsic-item-contributions
974                        // Spec modification: https://www.w3.org/TR/css-flexbox-1/#change-2016-max-contribution
975                        // Issue: https://github.com/w3c/csswg-drafts/issues/1435
976                        // Gentest: padding_border_overrides_size_flex_basis_0.html
977                        let clamping_basis = Some(item.flex_basis).maybe_max(style_preferred);
978                        let flex_basis_min = clamping_basis.filter(|_| item.flex_shrink == 0.0);
979                        let flex_basis_max = clamping_basis.filter(|_| item.flex_grow == 0.0);
980
981                        let min_main_size = style_min
982                            .maybe_max(flex_basis_min)
983                            .or(flex_basis_min)
984                            .unwrap_or(item.resolved_minimum_main_size)
985                            .max(item.resolved_minimum_main_size);
986                        let max_main_size =
987                            style_max.maybe_min(flex_basis_max).or(flex_basis_max).unwrap_or(f32::INFINITY);
988
989                        let content_contribution = match (min_main_size, style_preferred, max_main_size) {
990                            // If the clamping values are such that max <= min, then we can avoid the expensive step of computing the content size
991                            // as we know that the clamping values will override it anyway
992                            (min, Some(pref), max) if max <= min || max <= pref => {
993                                pref.min(max).max(min) + item.margin.main_axis_sum(constants.dir)
994                            }
995                            (min, _, max) if max <= min => min + item.margin.main_axis_sum(constants.dir),
996
997                            // Else compute the min- or -max content size and apply the full formula for computing the
998                            // min- or max- content contribution
999                            _ if item.is_scroll_container() => {
1000                                item.flex_basis + item.margin.main_axis_sum(constants.dir)
1001                            }
1002                            _ => {
1003                                // Parent size for child sizing
1004                                let cross_axis_parent_size = constants.node_inner_size.cross(dir);
1005
1006                                // Available space for child sizing
1007                                let cross_axis_margin_sum = constants.margin.cross_axis_sum(dir);
1008                                let child_min_cross = item.min_size.cross(dir).maybe_add(cross_axis_margin_sum);
1009                                let child_max_cross = item.max_size.cross(dir).maybe_add(cross_axis_margin_sum);
1010                                let cross_axis_available_space: AvailableSpace = available_space
1011                                    .cross(dir)
1012                                    .map_definite_value(|val| cross_axis_parent_size.unwrap_or(val))
1013                                    .maybe_clamp(child_min_cross, child_max_cross);
1014
1015                                let child_available_space = available_space.with_cross(dir, cross_axis_available_space);
1016
1017                                // Known dimensions for child sizing
1018                                let child_known_dimensions = {
1019                                    let mut ckd = item.size.with_main(dir, None);
1020                                    if item.align_self == AlignSelf::Stretch && ckd.cross(dir).is_none() {
1021                                        ckd.set_cross(
1022                                            dir,
1023                                            cross_axis_available_space
1024                                                .into_option()
1025                                                .maybe_sub(item.margin.cross_axis_sum(dir)),
1026                                        );
1027                                    }
1028                                    ckd
1029                                };
1030
1031                                // Either the min- or max- content size depending on which constraint we are sizing under.
1032                                // TODO: Optimise by using already computed values where available
1033                                debug_log!("COMPUTE CHILD BASE SIZE (for intrinsic main size):");
1034                                let content_main_size = tree.measure_child_size(
1035                                    item.node,
1036                                    child_known_dimensions,
1037                                    constants.node_inner_size,
1038                                    child_available_space,
1039                                    SizingMode::InherentSize,
1040                                    dir.main_axis(),
1041                                    Line::FALSE,
1042                                ) + item.margin.main_axis_sum(constants.dir);
1043
1044                                // This is somewhat bizarre in that it's asymmetrical depending whether the flex container is a column or a row.
1045                                //
1046                                // I *think* this might relate to https://drafts.csswg.org/css-flexbox-1/#algo-main-container:
1047                                //
1048                                //    "The automatic block size of a block-level flex container is its max-content size."
1049                                //
1050                                // Which could suggest that flex-basis defining a vertical size does not shrink because it is in the block axis, and the automatic size
1051                                // in the block axis is a MAX content size. Whereas a flex-basis defining a horizontal size does shrink because the automatic size in
1052                                // inline axis is MIN content size (although I don't have a reference for that).
1053                                //
1054                                // Ultimately, this was not found by reading the spec, but by trial and error fixing tests to align with Webkit/Firefox output.
1055                                // (see the `flex_basis_unconstraint_row` and `flex_basis_uncontraint_column` generated tests which demonstrate this)
1056                                if constants.is_row {
1057                                    content_main_size.maybe_clamp(style_min, style_max).max(main_content_box_inset)
1058                                } else {
1059                                    content_main_size
1060                                        .max(item.flex_basis)
1061                                        .maybe_clamp(style_min, style_max)
1062                                        .max(main_content_box_inset)
1063                                }
1064                            }
1065                        };
1066                        item.content_flex_fraction = {
1067                            let diff = content_contribution - item.flex_basis;
1068                            if diff > 0.0 {
1069                                diff / f32_max(1.0, item.flex_grow)
1070                            } else if diff < 0.0 {
1071                                let scaled_shrink_factor = f32_max(1.0, item.flex_shrink * item.inner_flex_basis);
1072                                diff / scaled_shrink_factor
1073                            } else {
1074                                // We are assuming that diff is 0.0 here and that we haven't accidentally introduced a NaN
1075                                0.0
1076                            }
1077                        };
1078                    }
1079
1080                    // TODO Spec says to scale everything by the line's max flex fraction. But neither Chrome nor firefox implement this
1081                    // so we don't either. But if we did want to, we'd need this computation here (and to use it below):
1082                    //
1083                    // Within each line, find the largest max-content flex fraction among all the flex items.
1084                    // let line_flex_fraction = line
1085                    //     .items
1086                    //     .iter()
1087                    //     .map(|item| item.content_flex_fraction)
1088                    //     .max_by(|a, b| a.total_cmp(b))
1089                    //     .unwrap_or(0.0); // Unwrap case never gets hit because there is always at least one item a line
1090
1091                    // Add each item’s flex base size to the product of:
1092                    //   - its flex grow factor (or scaled flex shrink factor,if the chosen max-content flex fraction was negative)
1093                    //   - the chosen max-content flex fraction
1094                    // then clamp that result by the max main size floored by the min main size.
1095                    //
1096                    // The flex container’s max-content size is the largest sum of the afore-calculated sizes of all items within a single line.
1097                    let item_main_size_sum = line
1098                        .items
1099                        .iter_mut()
1100                        .map(|item| {
1101                            let flex_fraction = item.content_flex_fraction;
1102                            // let flex_fraction = line_flex_fraction;
1103
1104                            let flex_contribution = if item.content_flex_fraction > 0.0 {
1105                                f32_max(1.0, item.flex_grow) * flex_fraction
1106                            } else if item.content_flex_fraction < 0.0 {
1107                                let scaled_shrink_factor = f32_max(1.0, item.flex_shrink) * item.inner_flex_basis;
1108                                scaled_shrink_factor * flex_fraction
1109                            } else {
1110                                0.0
1111                            };
1112                            let size = item.flex_basis + flex_contribution;
1113                            item.outer_target_size.set_main(constants.dir, size);
1114                            item.target_size.set_main(constants.dir, size);
1115                            size
1116                        })
1117                        .sum::<f32>();
1118
1119                    let gap_sum = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1120                    main_size = f32_max(main_size, item_main_size_sum + gap_sum)
1121                }
1122
1123                main_size + main_content_box_inset
1124            }
1125        }
1126    });
1127
1128    let outer_main_size = outer_main_size
1129        .maybe_clamp(constants.min_size.main(constants.dir), constants.max_size.main(constants.dir))
1130        .max(main_content_box_inset - constants.scrollbar_gutter.main(constants.dir));
1131
1132    // let outer_main_size = inner_main_size + constants.padding_border.main_axis_sum(constants.dir);
1133    let inner_main_size = f32_max(outer_main_size - main_content_box_inset, 0.0);
1134    constants.container_size.set_main(constants.dir, outer_main_size);
1135    constants.inner_container_size.set_main(constants.dir, inner_main_size);
1136    constants.node_inner_size.set_main(constants.dir, Some(inner_main_size));
1137}
1138
1139/// Resolve the flexible lengths of the items within a flex line.
1140/// Sets the `main` component of each item's `target_size` and `outer_target_size`
1141///
1142/// # [9.7. Resolving Flexible Lengths](https://www.w3.org/TR/css-flexbox-1/#resolve-flexible-lengths)
1143#[inline]
1144fn resolve_flexible_lengths(line: &mut FlexLine, constants: &AlgoConstants) {
1145    let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1146
1147    // 1. Determine the used flex factor. Sum the outer hypothetical main sizes of all
1148    //    items on the line. If the sum is less than the flex container’s inner main size,
1149    //    use the flex grow factor for the rest of this algorithm; otherwise, use the
1150    //    flex shrink factor.
1151
1152    let total_hypothetical_outer_main_size =
1153        line.items.iter().map(|child| child.hypothetical_outer_size.main(constants.dir)).sum::<f32>();
1154    let used_flex_factor: f32 = total_main_axis_gap + total_hypothetical_outer_main_size;
1155    let growing = used_flex_factor < constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1156    let shrinking = used_flex_factor > constants.node_inner_size.main(constants.dir).unwrap_or(0.0);
1157    let exactly_sized = !growing & !shrinking;
1158
1159    // 2. Size inflexible items. Freeze, setting its target main size to its hypothetical main size
1160    //    - Any item that has a flex factor of zero
1161    //    - If using the flex grow factor: any item that has a flex base size
1162    //      greater than its hypothetical main size
1163    //    - If using the flex shrink factor: any item that has a flex base size
1164    //      smaller than its hypothetical main size
1165
1166    for child in line.items.iter_mut() {
1167        let inner_target_size = child.hypothetical_inner_size.main(constants.dir);
1168        child.target_size.set_main(constants.dir, inner_target_size);
1169
1170        if exactly_sized
1171            || (child.flex_grow == 0.0 && child.flex_shrink == 0.0)
1172            || (growing && child.flex_basis > child.hypothetical_inner_size.main(constants.dir))
1173            || (shrinking && child.flex_basis < child.hypothetical_inner_size.main(constants.dir))
1174        {
1175            child.frozen = true;
1176            let outer_target_size = inner_target_size + child.margin.main_axis_sum(constants.dir);
1177            child.outer_target_size.set_main(constants.dir, outer_target_size);
1178        }
1179    }
1180
1181    if exactly_sized {
1182        return;
1183    }
1184
1185    // 3. Calculate initial free space. Sum the outer sizes of all items on the line,
1186    //    and subtract this from the flex container’s inner main size. For frozen items,
1187    //    use their outer target main size; for other items, use their outer flex base size.
1188
1189    let used_space: f32 = total_main_axis_gap
1190        + line
1191            .items
1192            .iter()
1193            .map(|child| {
1194                if child.frozen {
1195                    child.outer_target_size.main(constants.dir)
1196                } else {
1197                    child.flex_basis + child.margin.main_axis_sum(constants.dir)
1198                }
1199            })
1200            .sum::<f32>();
1201
1202    let initial_free_space = constants.node_inner_size.main(constants.dir).maybe_sub(used_space).unwrap_or(0.0);
1203
1204    // 4. Loop
1205
1206    loop {
1207        // a. Check for flexible items. If all the flex items on the line are frozen,
1208        //    free space has been distributed; exit this loop.
1209
1210        if line.items.iter().all(|child| child.frozen) {
1211            break;
1212        }
1213
1214        // b. Calculate the remaining free space as for initial free space, above.
1215        //    If the sum of the unfrozen flex items’ flex factors is less than one,
1216        //    multiply the initial free space by this sum. If the magnitude of this
1217        //    value is less than the magnitude of the remaining free space, use this
1218        //    as the remaining free space.
1219
1220        let used_space: f32 = total_main_axis_gap
1221            + line
1222                .items
1223                .iter()
1224                .map(|child| {
1225                    if child.frozen {
1226                        child.outer_target_size.main(constants.dir)
1227                    } else {
1228                        child.flex_basis + child.margin.main_axis_sum(constants.dir)
1229                    }
1230                })
1231                .sum::<f32>();
1232
1233        let mut unfrozen: Vec<&mut FlexItem> = line.items.iter_mut().filter(|child| !child.frozen).collect();
1234
1235        let (sum_flex_grow, sum_flex_shrink): (f32, f32) =
1236            unfrozen.iter().fold((0.0, 0.0), |(flex_grow, flex_shrink), item| {
1237                (flex_grow + item.flex_grow, flex_shrink + item.flex_shrink)
1238            });
1239
1240        let free_space = if growing && sum_flex_grow < 1.0 {
1241            (initial_free_space * sum_flex_grow - total_main_axis_gap)
1242                .maybe_min(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1243        } else if shrinking && sum_flex_shrink < 1.0 {
1244            (initial_free_space * sum_flex_shrink - total_main_axis_gap)
1245                .maybe_max(constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1246        } else {
1247            (constants.node_inner_size.main(constants.dir).maybe_sub(used_space))
1248                .unwrap_or(used_flex_factor - used_space)
1249        };
1250
1251        // c. Distribute free space proportional to the flex factors.
1252        //    - If the remaining free space is zero
1253        //        Do Nothing
1254        //    - If using the flex grow factor
1255        //        Find the ratio of the item’s flex grow factor to the sum of the
1256        //        flex grow factors of all unfrozen items on the line. Set the item’s
1257        //        target main size to its flex base size plus a fraction of the remaining
1258        //        free space proportional to the ratio.
1259        //    - If using the flex shrink factor
1260        //        For every unfrozen item on the line, multiply its flex shrink factor by
1261        //        its inner flex base size, and note this as its scaled flex shrink factor.
1262        //        Find the ratio of the item’s scaled flex shrink factor to the sum of the
1263        //        scaled flex shrink factors of all unfrozen items on the line. Set the item’s
1264        //        target main size to its flex base size minus a fraction of the absolute value
1265        //        of the remaining free space proportional to the ratio. Note this may result
1266        //        in a negative inner main size; it will be corrected in the next step.
1267        //    - Otherwise
1268        //        Do Nothing
1269
1270        if free_space.is_normal() {
1271            if growing && sum_flex_grow > 0.0 {
1272                for child in &mut unfrozen {
1273                    child
1274                        .target_size
1275                        .set_main(constants.dir, child.flex_basis + free_space * (child.flex_grow / sum_flex_grow));
1276                }
1277            } else if shrinking && sum_flex_shrink > 0.0 {
1278                let sum_scaled_shrink_factor: f32 =
1279                    unfrozen.iter().map(|child| child.inner_flex_basis * child.flex_shrink).sum();
1280
1281                if sum_scaled_shrink_factor > 0.0 {
1282                    for child in &mut unfrozen {
1283                        let scaled_shrink_factor = child.inner_flex_basis * child.flex_shrink;
1284                        child.target_size.set_main(
1285                            constants.dir,
1286                            child.flex_basis + free_space * (scaled_shrink_factor / sum_scaled_shrink_factor),
1287                        )
1288                    }
1289                }
1290            }
1291        }
1292
1293        // d. Fix min/max violations. Clamp each non-frozen item’s target main size by its
1294        //    used min and max main sizes and floor its content-box size at zero. If the
1295        //    item’s target main size was made smaller by this, it’s a max violation.
1296        //    If the item’s target main size was made larger by this, it’s a min violation.
1297
1298        let total_violation = unfrozen.iter_mut().fold(0.0, |acc, child| -> f32 {
1299            let resolved_min_main: Option<f32> = child.resolved_minimum_main_size.into();
1300            let max_main = child.max_size.main(constants.dir);
1301            let clamped = child.target_size.main(constants.dir).maybe_clamp(resolved_min_main, max_main).max(0.0);
1302            child.violation = clamped - child.target_size.main(constants.dir);
1303            child.target_size.set_main(constants.dir, clamped);
1304            child.outer_target_size.set_main(
1305                constants.dir,
1306                child.target_size.main(constants.dir) + child.margin.main_axis_sum(constants.dir),
1307            );
1308
1309            acc + child.violation
1310        });
1311
1312        // e. Freeze over-flexed items. The total violation is the sum of the adjustments
1313        //    from the previous step ∑(clamped size - unclamped size). If the total violation is:
1314        //    - Zero
1315        //        Freeze all items.
1316        //    - Positive
1317        //        Freeze all the items with min violations.
1318        //    - Negative
1319        //        Freeze all the items with max violations.
1320
1321        for child in &mut unfrozen {
1322            match total_violation {
1323                v if v > 0.0 => child.frozen = child.violation > 0.0,
1324                v if v < 0.0 => child.frozen = child.violation < 0.0,
1325                _ => child.frozen = true,
1326            }
1327        }
1328
1329        // f. Return to the start of this loop.
1330    }
1331}
1332
1333/// Determine the hypothetical cross size of each item.
1334///
1335/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1336///
1337/// - [**Determine the hypothetical cross size of each item**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-item)
1338///     by performing layout with the used main size and the available space, treating auto as fit-content.
1339#[inline]
1340fn determine_hypothetical_cross_size(
1341    tree: &mut impl LayoutFlexboxContainer,
1342    line: &mut FlexLine,
1343    constants: &AlgoConstants,
1344    available_space: Size<AvailableSpace>,
1345) {
1346    for child in line.items.iter_mut() {
1347        let padding_border_sum = (child.padding + child.border).cross_axis_sum(constants.dir);
1348
1349        let child_known_main = constants.container_size.main(constants.dir).into();
1350
1351        let child_cross = child
1352            .size
1353            .cross(constants.dir)
1354            .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1355            .maybe_max(padding_border_sum);
1356
1357        let child_available_cross = available_space
1358            .cross(constants.dir)
1359            .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1360            .maybe_max(padding_border_sum);
1361
1362        let child_inner_cross = child_cross.unwrap_or_else(|| {
1363            tree.measure_child_size(
1364                child.node,
1365                Size {
1366                    width: if constants.is_row { child.target_size.width.into() } else { child_cross },
1367                    height: if constants.is_row { child_cross } else { child.target_size.height.into() },
1368                },
1369                constants.node_inner_size,
1370                Size {
1371                    width: if constants.is_row { child_known_main } else { child_available_cross },
1372                    height: if constants.is_row { child_available_cross } else { child_known_main },
1373                },
1374                SizingMode::ContentSize,
1375                constants.dir.cross_axis(),
1376                Line::FALSE,
1377            )
1378            .maybe_clamp(child.min_size.cross(constants.dir), child.max_size.cross(constants.dir))
1379            .max(padding_border_sum)
1380        });
1381        let child_outer_cross = child_inner_cross + child.margin.cross_axis_sum(constants.dir);
1382
1383        child.hypothetical_inner_size.set_cross(constants.dir, child_inner_cross);
1384        child.hypothetical_outer_size.set_cross(constants.dir, child_outer_cross);
1385    }
1386}
1387
1388/// Calculate the base lines of the children.
1389#[inline]
1390fn calculate_children_base_lines(
1391    tree: &mut impl LayoutFlexboxContainer,
1392    node_size: Size<Option<f32>>,
1393    available_space: Size<AvailableSpace>,
1394    flex_lines: &mut [FlexLine],
1395    constants: &AlgoConstants,
1396) {
1397    // Only compute baselines for flex rows because we only support baseline alignment in the cross axis
1398    // where that axis is also the inline axis
1399    // TODO: this may need revisiting if/when we support vertical writing modes
1400    if !constants.is_row {
1401        return;
1402    }
1403
1404    for line in flex_lines {
1405        // If a flex line has one or zero items participating in baseline alignment then baseline alignment is a no-op so we skip
1406        let line_baseline_child_count =
1407            line.items.iter().filter(|child| child.align_self == AlignSelf::Baseline).count();
1408        if line_baseline_child_count <= 1 {
1409            continue;
1410        }
1411
1412        for child in line.items.iter_mut() {
1413            // Only calculate baselines for children participating in baseline alignment
1414            if child.align_self != AlignSelf::Baseline {
1415                continue;
1416            }
1417
1418            let measured_size_and_baselines = tree.perform_child_layout(
1419                child.node,
1420                Size {
1421                    width: if constants.is_row {
1422                        child.target_size.width.into()
1423                    } else {
1424                        child.hypothetical_inner_size.width.into()
1425                    },
1426                    height: if constants.is_row {
1427                        child.hypothetical_inner_size.height.into()
1428                    } else {
1429                        child.target_size.height.into()
1430                    },
1431                },
1432                constants.node_inner_size,
1433                Size {
1434                    width: if constants.is_row {
1435                        constants.container_size.width.into()
1436                    } else {
1437                        available_space.width.maybe_set(node_size.width)
1438                    },
1439                    height: if constants.is_row {
1440                        available_space.height.maybe_set(node_size.height)
1441                    } else {
1442                        constants.container_size.height.into()
1443                    },
1444                },
1445                SizingMode::ContentSize,
1446                Line::FALSE,
1447            );
1448
1449            let baseline = measured_size_and_baselines.first_baselines.y;
1450            let height = measured_size_and_baselines.size.height;
1451
1452            child.baseline = baseline.unwrap_or(height) + child.margin.top;
1453        }
1454    }
1455}
1456
1457/// Calculate the cross size of each flex line.
1458///
1459/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1460///
1461/// - [**Calculate the cross size of each flex line**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-line).
1462#[inline]
1463fn calculate_cross_size(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1464    // If the flex container is single-line and has a definite cross size,
1465    // the cross size of the flex line is the flex container’s inner cross size.
1466    if !constants.is_wrap && node_size.cross(constants.dir).is_some() {
1467        let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1468        let cross_min_size = constants.min_size.cross(constants.dir);
1469        let cross_max_size = constants.max_size.cross(constants.dir);
1470        flex_lines[0].cross_size = node_size
1471            .cross(constants.dir)
1472            .maybe_clamp(cross_min_size, cross_max_size)
1473            .maybe_sub(cross_axis_padding_border)
1474            .maybe_max(0.0)
1475            .unwrap_or(0.0);
1476    } else {
1477        // Otherwise, for each flex line:
1478        //
1479        //    1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose
1480        //       align-self is baseline, and whose cross-axis margins are both non-auto. Find the
1481        //       largest of the distances between each item’s baseline and its hypothetical outer
1482        //       cross-start edge, and the largest of the distances between each item’s baseline
1483        //       and its hypothetical outer cross-end edge, and sum these two values.
1484
1485        //    2. Among all the items not collected by the previous step, find the largest
1486        //       outer hypothetical cross size.
1487
1488        //    3. The used cross-size of the flex line is the largest of the numbers found in the
1489        //       previous two steps and zero.
1490        for line in flex_lines.iter_mut() {
1491            let max_baseline: f32 = line.items.iter().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1492            line.cross_size = line
1493                .items
1494                .iter()
1495                .map(|child| {
1496                    if child.align_self == AlignSelf::Baseline
1497                        && !child.margin_is_auto.cross_start(constants.dir)
1498                        && !child.margin_is_auto.cross_end(constants.dir)
1499                    {
1500                        max_baseline - child.baseline + child.hypothetical_outer_size.cross(constants.dir)
1501                    } else {
1502                        child.hypothetical_outer_size.cross(constants.dir)
1503                    }
1504                })
1505                .fold(0.0, |acc, x| acc.max(x));
1506        }
1507
1508        // 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.
1509        // Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically.
1510        if !constants.is_wrap {
1511            let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1512            let cross_min_size = constants.min_size.cross(constants.dir);
1513            let cross_max_size = constants.max_size.cross(constants.dir);
1514            flex_lines[0].cross_size = flex_lines[0].cross_size.maybe_clamp(
1515                cross_min_size.maybe_sub(cross_axis_padding_border),
1516                cross_max_size.maybe_sub(cross_axis_padding_border),
1517            );
1518        }
1519    }
1520}
1521
1522/// Handle 'align-content: stretch'.
1523///
1524/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1525///
1526/// - [**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,
1527///     and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size,
1528///     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.
1529#[inline]
1530fn handle_align_content_stretch(flex_lines: &mut [FlexLine], node_size: Size<Option<f32>>, constants: &AlgoConstants) {
1531    if constants.align_content == AlignContent::Stretch {
1532        let cross_axis_padding_border = constants.content_box_inset.cross_axis_sum(constants.dir);
1533        let cross_min_size = constants.min_size.cross(constants.dir);
1534        let cross_max_size = constants.max_size.cross(constants.dir);
1535        let container_min_inner_cross = node_size
1536            .cross(constants.dir)
1537            .or(cross_min_size)
1538            .maybe_clamp(cross_min_size, cross_max_size)
1539            .maybe_sub(cross_axis_padding_border)
1540            .maybe_max(0.0)
1541            .unwrap_or(0.0);
1542
1543        let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1544        let lines_total_cross: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>() + total_cross_axis_gap;
1545
1546        if lines_total_cross < container_min_inner_cross {
1547            let remaining = container_min_inner_cross - lines_total_cross;
1548            let addition = remaining / flex_lines.len() as f32;
1549            flex_lines.iter_mut().for_each(|line| line.cross_size += addition);
1550        }
1551    }
1552}
1553
1554/// Determine the used cross size of each flex item.
1555///
1556/// # [9.4. Cross Size Determination](https://www.w3.org/TR/css-flexbox-1/#cross-sizing)
1557///
1558/// - [**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,
1559///     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.
1560///     Otherwise, the used cross size is the item’s hypothetical cross size.
1561///
1562///     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.
1563///
1564///     **Note that this step does not affect the main size of the flex item, even if it has an intrinsic aspect ratio**.
1565#[inline]
1566fn determine_used_cross_size(
1567    tree: &impl LayoutFlexboxContainer,
1568    flex_lines: &mut [FlexLine],
1569    constants: &AlgoConstants,
1570) {
1571    for line in flex_lines {
1572        let line_cross_size = line.cross_size;
1573
1574        for child in line.items.iter_mut() {
1575            let child_style = tree.get_flexbox_child_style(child.node);
1576            child.target_size.set_cross(
1577                constants.dir,
1578                if child.align_self == AlignSelf::Stretch
1579                    && !child.margin_is_auto.cross_start(constants.dir)
1580                    && !child.margin_is_auto.cross_end(constants.dir)
1581                    && child_style.size().cross(constants.dir) == Dimension::Auto
1582                {
1583                    // For some reason this particular usage of max_width is an exception to the rule that max_width's transfer
1584                    // using the aspect_ratio (if set). Both Chrome and Firefox agree on this. And reading the spec, it seems like
1585                    // a reasonable interpretation. Although it seems to me that the spec *should* apply aspect_ratio here.
1586                    let padding = child_style.padding().resolve_or_zero(constants.node_inner_size);
1587                    let border = child_style.border().resolve_or_zero(constants.node_inner_size);
1588                    let pb_sum = (padding + border).sum_axes();
1589                    let box_sizing_adjustment =
1590                        if child_style.box_sizing() == BoxSizing::ContentBox { pb_sum } else { Size::ZERO };
1591
1592                    let max_size_ignoring_aspect_ratio = child_style
1593                        .max_size()
1594                        .maybe_resolve(constants.node_inner_size)
1595                        .maybe_add(box_sizing_adjustment);
1596
1597                    (line_cross_size - child.margin.cross_axis_sum(constants.dir)).maybe_clamp(
1598                        child.min_size.cross(constants.dir),
1599                        max_size_ignoring_aspect_ratio.cross(constants.dir),
1600                    )
1601                } else {
1602                    child.hypothetical_inner_size.cross(constants.dir)
1603                },
1604            );
1605
1606            child.outer_target_size.set_cross(
1607                constants.dir,
1608                child.target_size.cross(constants.dir) + child.margin.cross_axis_sum(constants.dir),
1609            );
1610        }
1611    }
1612}
1613
1614/// Distribute any remaining free space.
1615///
1616/// # [9.5. Main-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#main-alignment)
1617///
1618/// - [**Distribute any remaining free space**](https://www.w3.org/TR/css-flexbox-1/#algo-main-align). For each flex line:
1619///
1620///     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.
1621///         Otherwise, set all `auto` margins to zero.
1622///
1623///     2. Align the items along the main-axis per `justify-content`.
1624#[inline]
1625fn distribute_remaining_free_space(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1626    for line in flex_lines {
1627        let total_main_axis_gap = sum_axis_gaps(constants.gap.main(constants.dir), line.items.len());
1628        let used_space: f32 = total_main_axis_gap
1629            + line.items.iter().map(|child| child.outer_target_size.main(constants.dir)).sum::<f32>();
1630        let free_space = constants.inner_container_size.main(constants.dir) - used_space;
1631        let mut num_auto_margins = 0;
1632
1633        for child in line.items.iter_mut() {
1634            if child.margin_is_auto.main_start(constants.dir) {
1635                num_auto_margins += 1;
1636            }
1637            if child.margin_is_auto.main_end(constants.dir) {
1638                num_auto_margins += 1;
1639            }
1640        }
1641
1642        if free_space > 0.0 && num_auto_margins > 0 {
1643            let margin = free_space / num_auto_margins as f32;
1644
1645            for child in line.items.iter_mut() {
1646                if child.margin_is_auto.main_start(constants.dir) {
1647                    if constants.is_row {
1648                        child.margin.left = margin;
1649                    } else {
1650                        child.margin.top = margin;
1651                    }
1652                }
1653                if child.margin_is_auto.main_end(constants.dir) {
1654                    if constants.is_row {
1655                        child.margin.right = margin;
1656                    } else {
1657                        child.margin.bottom = margin;
1658                    }
1659                }
1660            }
1661        } else {
1662            let num_items = line.items.len();
1663            let layout_reverse = constants.dir.is_reverse();
1664            let gap = constants.gap.main(constants.dir);
1665            let is_safe = false; // TODO: Implement safe alignment
1666            let raw_justify_content_mode = constants.justify_content.unwrap_or(JustifyContent::FlexStart);
1667            let justify_content_mode =
1668                apply_alignment_fallback(free_space, num_items, raw_justify_content_mode, is_safe);
1669
1670            let justify_item = |(i, child): (usize, &mut FlexItem)| {
1671                child.offset_main =
1672                    compute_alignment_offset(free_space, num_items, gap, justify_content_mode, layout_reverse, i == 0);
1673            };
1674
1675            if layout_reverse {
1676                line.items.iter_mut().rev().enumerate().for_each(justify_item);
1677            } else {
1678                line.items.iter_mut().enumerate().for_each(justify_item);
1679            }
1680        }
1681    }
1682}
1683
1684/// Resolve cross-axis `auto` margins.
1685///
1686/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1687///
1688/// - [**Resolve cross-axis `auto` margins**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-margins).
1689///     If a flex item has auto cross-axis margins:
1690///
1691///     - If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line,
1692///         distribute the difference in those sizes equally to the auto margins.
1693///
1694///     - Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero.
1695///         Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line.
1696#[inline]
1697fn resolve_cross_axis_auto_margins(flex_lines: &mut [FlexLine], constants: &AlgoConstants) {
1698    for line in flex_lines {
1699        let line_cross_size = line.cross_size;
1700        let max_baseline: f32 = line.items.iter_mut().map(|child| child.baseline).fold(0.0, |acc, x| acc.max(x));
1701
1702        for child in line.items.iter_mut() {
1703            let free_space = line_cross_size - child.outer_target_size.cross(constants.dir);
1704
1705            if child.margin_is_auto.cross_start(constants.dir) && child.margin_is_auto.cross_end(constants.dir) {
1706                if constants.is_row {
1707                    child.margin.top = free_space / 2.0;
1708                    child.margin.bottom = free_space / 2.0;
1709                } else {
1710                    child.margin.left = free_space / 2.0;
1711                    child.margin.right = free_space / 2.0;
1712                }
1713            } else if child.margin_is_auto.cross_start(constants.dir) {
1714                if constants.is_row {
1715                    child.margin.top = free_space;
1716                } else {
1717                    child.margin.left = free_space;
1718                }
1719            } else if child.margin_is_auto.cross_end(constants.dir) {
1720                if constants.is_row {
1721                    child.margin.bottom = free_space;
1722                } else {
1723                    child.margin.right = free_space;
1724                }
1725            } else {
1726                // 14. Align all flex items along the cross-axis.
1727                child.offset_cross = align_flex_items_along_cross_axis(child, free_space, max_baseline, constants);
1728            }
1729        }
1730    }
1731}
1732
1733/// Align all flex items along the cross-axis.
1734///
1735/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1736///
1737/// - [**Align all flex items along the cross-axis**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-align) per `align-self`,
1738///     if neither of the item's cross-axis margins are `auto`.
1739#[inline]
1740fn align_flex_items_along_cross_axis(
1741    child: &FlexItem,
1742    free_space: f32,
1743    max_baseline: f32,
1744    constants: &AlgoConstants,
1745) -> f32 {
1746    match child.align_self {
1747        AlignSelf::Start => 0.0,
1748        AlignSelf::FlexStart => {
1749            if constants.is_wrap_reverse {
1750                free_space
1751            } else {
1752                0.0
1753            }
1754        }
1755        AlignSelf::End => free_space,
1756        AlignSelf::FlexEnd => {
1757            if constants.is_wrap_reverse {
1758                0.0
1759            } else {
1760                free_space
1761            }
1762        }
1763        AlignSelf::Center => free_space / 2.0,
1764        AlignSelf::Baseline => {
1765            if constants.is_row {
1766                max_baseline - child.baseline
1767            } else {
1768                // Until we support vertical writing modes, baseline alignment only makes sense if
1769                // the constants.direction is row, so we treat it as flex-start alignment in columns.
1770                if constants.is_wrap_reverse {
1771                    free_space
1772                } else {
1773                    0.0
1774                }
1775            }
1776        }
1777        AlignSelf::Stretch => {
1778            if constants.is_wrap_reverse {
1779                free_space
1780            } else {
1781                0.0
1782            }
1783        }
1784    }
1785}
1786
1787/// Determine the flex container’s used cross size.
1788///
1789/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1790///
1791/// - [**Determine the flex container’s used cross size**](https://www.w3.org/TR/css-flexbox-1/#algo-cross-container):
1792///
1793///     - If the cross size property is a definite size, use that, clamped by the used min and max cross sizes of the flex container.
1794///
1795///     - Otherwise, use the sum of the flex lines' cross sizes, clamped by the used min and max cross sizes of the flex container.
1796#[inline]
1797#[must_use]
1798fn determine_container_cross_size(
1799    flex_lines: &[FlexLine],
1800    node_size: Size<Option<f32>>,
1801    constants: &mut AlgoConstants,
1802) -> f32 {
1803    let total_cross_axis_gap = sum_axis_gaps(constants.gap.cross(constants.dir), flex_lines.len());
1804    let total_line_cross_size: f32 = flex_lines.iter().map(|line| line.cross_size).sum::<f32>();
1805
1806    let padding_border_sum = constants.content_box_inset.cross_axis_sum(constants.dir);
1807    let cross_scrollbar_gutter = constants.scrollbar_gutter.cross(constants.dir);
1808    let min_cross_size = constants.min_size.cross(constants.dir);
1809    let max_cross_size = constants.max_size.cross(constants.dir);
1810    let outer_container_size = node_size
1811        .cross(constants.dir)
1812        .unwrap_or(total_line_cross_size + total_cross_axis_gap + padding_border_sum)
1813        .maybe_clamp(min_cross_size, max_cross_size)
1814        .max(padding_border_sum - cross_scrollbar_gutter);
1815    let inner_container_size = f32_max(outer_container_size - padding_border_sum, 0.0);
1816
1817    constants.container_size.set_cross(constants.dir, outer_container_size);
1818    constants.inner_container_size.set_cross(constants.dir, inner_container_size);
1819
1820    total_line_cross_size
1821}
1822
1823/// Align all flex lines per `align-content`.
1824///
1825/// # [9.6. Cross-Axis Alignment](https://www.w3.org/TR/css-flexbox-1/#cross-alignment)
1826///
1827/// - [**Align all flex lines**](https://www.w3.org/TR/css-flexbox-1/#algo-line-align) per `align-content`.
1828#[inline]
1829fn align_flex_lines_per_align_content(flex_lines: &mut [FlexLine], constants: &AlgoConstants, total_cross_size: f32) {
1830    let num_lines = flex_lines.len();
1831    let gap = constants.gap.cross(constants.dir);
1832    let total_cross_axis_gap = sum_axis_gaps(gap, num_lines);
1833    let free_space = constants.inner_container_size.cross(constants.dir) - total_cross_size - total_cross_axis_gap;
1834    let is_safe = false; // TODO: Implement safe alignment
1835
1836    let align_content_mode = apply_alignment_fallback(free_space, num_lines, constants.align_content, is_safe);
1837
1838    let align_line = |(i, line): (usize, &mut FlexLine)| {
1839        line.offset_cross =
1840            compute_alignment_offset(free_space, num_lines, gap, align_content_mode, constants.is_wrap_reverse, i == 0);
1841    };
1842
1843    if constants.is_wrap_reverse {
1844        flex_lines.iter_mut().rev().enumerate().for_each(align_line);
1845    } else {
1846        flex_lines.iter_mut().enumerate().for_each(align_line);
1847    }
1848}
1849
1850/// Calculates the layout for a flex-item
1851#[allow(clippy::too_many_arguments)]
1852fn calculate_flex_item(
1853    tree: &mut impl LayoutFlexboxContainer,
1854    item: &mut FlexItem,
1855    total_offset_main: &mut f32,
1856    total_offset_cross: f32,
1857    line_offset_cross: f32,
1858    #[cfg(feature = "content_size")] total_content_size: &mut Size<f32>,
1859    container_size: Size<f32>,
1860    node_inner_size: Size<Option<f32>>,
1861    direction: FlexDirection,
1862) {
1863    let layout_output = tree.perform_child_layout(
1864        item.node,
1865        item.target_size.map(|s| s.into()),
1866        node_inner_size,
1867        container_size.map(|s| s.into()),
1868        SizingMode::ContentSize,
1869        Line::FALSE,
1870    );
1871    let LayoutOutput {
1872        size,
1873        #[cfg(feature = "content_size")]
1874        content_size,
1875        ..
1876    } = layout_output;
1877
1878    let offset_main = *total_offset_main
1879        + item.offset_main
1880        + item.margin.main_start(direction)
1881        + (item.inset.main_start(direction).or(item.inset.main_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1882
1883    let offset_cross = total_offset_cross
1884        + item.offset_cross
1885        + line_offset_cross
1886        + item.margin.cross_start(direction)
1887        + (item.inset.cross_start(direction).or(item.inset.cross_end(direction).map(|pos| -pos)).unwrap_or(0.0));
1888
1889    if direction.is_row() {
1890        let baseline_offset_cross = total_offset_cross + item.offset_cross + item.margin.cross_start(direction);
1891        let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1892        item.baseline = baseline_offset_cross + inner_baseline;
1893    } else {
1894        let baseline_offset_main = *total_offset_main + item.offset_main + item.margin.main_start(direction);
1895        let inner_baseline = layout_output.first_baselines.y.unwrap_or(size.height);
1896        item.baseline = baseline_offset_main + inner_baseline;
1897    }
1898
1899    let location = match direction.is_row() {
1900        true => Point { x: offset_main, y: offset_cross },
1901        false => Point { x: offset_cross, y: offset_main },
1902    };
1903    let scrollbar_size = Size {
1904        width: if item.overflow.y == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1905        height: if item.overflow.x == Overflow::Scroll { item.scrollbar_width } else { 0.0 },
1906    };
1907
1908    tree.set_unrounded_layout(
1909        item.node,
1910        &Layout {
1911            order: item.order,
1912            size,
1913            #[cfg(feature = "content_size")]
1914            content_size,
1915            scrollbar_size,
1916            location,
1917            padding: item.padding,
1918            border: item.border,
1919            margin: item.margin,
1920        },
1921    );
1922
1923    *total_offset_main += item.offset_main + item.margin.main_axis_sum(direction) + size.main(direction);
1924
1925    #[cfg(feature = "content_size")]
1926    {
1927        *total_content_size =
1928            total_content_size.f32_max(compute_content_size_contribution(location, size, content_size, item.overflow));
1929    }
1930}
1931
1932/// Calculates the layout line
1933#[allow(clippy::too_many_arguments)]
1934fn calculate_layout_line(
1935    tree: &mut impl LayoutFlexboxContainer,
1936    line: &mut FlexLine,
1937    total_offset_cross: &mut f32,
1938    #[cfg(feature = "content_size")] content_size: &mut Size<f32>,
1939    container_size: Size<f32>,
1940    node_inner_size: Size<Option<f32>>,
1941    padding_border: Rect<f32>,
1942    direction: FlexDirection,
1943) {
1944    let mut total_offset_main = padding_border.main_start(direction);
1945    let line_offset_cross = line.offset_cross;
1946
1947    if direction.is_reverse() {
1948        for item in line.items.iter_mut().rev() {
1949            calculate_flex_item(
1950                tree,
1951                item,
1952                &mut total_offset_main,
1953                *total_offset_cross,
1954                line_offset_cross,
1955                #[cfg(feature = "content_size")]
1956                content_size,
1957                container_size,
1958                node_inner_size,
1959                direction,
1960            );
1961        }
1962    } else {
1963        for item in line.items.iter_mut() {
1964            calculate_flex_item(
1965                tree,
1966                item,
1967                &mut total_offset_main,
1968                *total_offset_cross,
1969                line_offset_cross,
1970                #[cfg(feature = "content_size")]
1971                content_size,
1972                container_size,
1973                node_inner_size,
1974                direction,
1975            );
1976        }
1977    }
1978
1979    *total_offset_cross += line_offset_cross + line.cross_size;
1980}
1981
1982/// Do a final layout pass and collect the resulting layouts.
1983#[inline]
1984fn final_layout_pass(
1985    tree: &mut impl LayoutFlexboxContainer,
1986    flex_lines: &mut [FlexLine],
1987    constants: &AlgoConstants,
1988) -> Size<f32> {
1989    let mut total_offset_cross = constants.content_box_inset.cross_start(constants.dir);
1990
1991    #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
1992    let mut content_size = Size::ZERO;
1993
1994    if constants.is_wrap_reverse {
1995        for line in flex_lines.iter_mut().rev() {
1996            calculate_layout_line(
1997                tree,
1998                line,
1999                &mut total_offset_cross,
2000                #[cfg(feature = "content_size")]
2001                &mut content_size,
2002                constants.container_size,
2003                constants.node_inner_size,
2004                constants.content_box_inset,
2005                constants.dir,
2006            );
2007        }
2008    } else {
2009        for line in flex_lines.iter_mut() {
2010            calculate_layout_line(
2011                tree,
2012                line,
2013                &mut total_offset_cross,
2014                #[cfg(feature = "content_size")]
2015                &mut content_size,
2016                constants.container_size,
2017                constants.node_inner_size,
2018                constants.content_box_inset,
2019                constants.dir,
2020            );
2021        }
2022    }
2023
2024    content_size.width += constants.content_box_inset.right - constants.border.right - constants.scrollbar_gutter.x;
2025    content_size.height += constants.content_box_inset.bottom - constants.border.bottom - constants.scrollbar_gutter.y;
2026
2027    content_size
2028}
2029
2030/// Perform absolute layout on all absolutely positioned children.
2031#[inline]
2032fn perform_absolute_layout_on_absolute_children(
2033    tree: &mut impl LayoutFlexboxContainer,
2034    node: NodeId,
2035    constants: &AlgoConstants,
2036) -> Size<f32> {
2037    let container_width = constants.container_size.width;
2038    let container_height = constants.container_size.height;
2039    let inset_relative_size =
2040        constants.container_size - constants.border.sum_axes() - constants.scrollbar_gutter.into();
2041
2042    #[cfg_attr(not(feature = "content_size"), allow(unused_mut))]
2043    let mut content_size = Size::ZERO;
2044
2045    for order in 0..tree.child_count(node) {
2046        let child = tree.get_child_id(node, order);
2047        let child_style = tree.get_flexbox_child_style(child);
2048
2049        // Skip items that are display:none or are not position:absolute
2050        if child_style.box_generation_mode() == BoxGenerationMode::None || child_style.position() != Position::Absolute
2051        {
2052            continue;
2053        }
2054
2055        let overflow = child_style.overflow();
2056        let scrollbar_width = child_style.scrollbar_width();
2057        let aspect_ratio = child_style.aspect_ratio();
2058        let align_self = child_style.align_self().unwrap_or(constants.align_items);
2059        let margin = child_style.margin().map(|margin| margin.resolve_to_option(inset_relative_size.width));
2060        let padding = child_style.padding().resolve_or_zero(Some(inset_relative_size.width));
2061        let border = child_style.border().resolve_or_zero(Some(inset_relative_size.width));
2062        let padding_border_sum = (padding + border).sum_axes();
2063        let box_sizing_adjustment =
2064            if child_style.box_sizing() == BoxSizing::ContentBox { padding_border_sum } else { Size::ZERO };
2065
2066        // Resolve inset
2067        // Insets are resolved against the container size minus border
2068        let left = child_style.inset().left.maybe_resolve(inset_relative_size.width);
2069        let right = child_style.inset().right.maybe_resolve(inset_relative_size.width);
2070        let top = child_style.inset().top.maybe_resolve(inset_relative_size.height);
2071        let bottom = child_style.inset().bottom.maybe_resolve(inset_relative_size.height);
2072
2073        // Compute known dimensions from min/max/inherent size styles
2074        let style_size = child_style
2075            .size()
2076            .maybe_resolve(inset_relative_size)
2077            .maybe_apply_aspect_ratio(aspect_ratio)
2078            .maybe_add(box_sizing_adjustment);
2079        let min_size = child_style
2080            .min_size()
2081            .maybe_resolve(inset_relative_size)
2082            .maybe_apply_aspect_ratio(aspect_ratio)
2083            .maybe_add(box_sizing_adjustment)
2084            .or(padding_border_sum.map(Some))
2085            .maybe_max(padding_border_sum);
2086        let max_size = child_style
2087            .max_size()
2088            .maybe_resolve(inset_relative_size)
2089            .maybe_apply_aspect_ratio(aspect_ratio)
2090            .maybe_add(box_sizing_adjustment);
2091        let mut known_dimensions = style_size.maybe_clamp(min_size, max_size);
2092
2093        drop(child_style);
2094
2095        // Fill in width from left/right and reapply aspect ratio if:
2096        //   - Width is not already known
2097        //   - Item has both left and right inset properties set
2098        if let (None, Some(left), Some(right)) = (known_dimensions.width, left, right) {
2099            let new_width_raw = inset_relative_size.width.maybe_sub(margin.left).maybe_sub(margin.right) - left - right;
2100            known_dimensions.width = Some(f32_max(new_width_raw, 0.0));
2101            known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2102        }
2103
2104        // Fill in height from top/bottom and reapply aspect ratio if:
2105        //   - Height is not already known
2106        //   - Item has both top and bottom inset properties set
2107        if let (None, Some(top), Some(bottom)) = (known_dimensions.height, top, bottom) {
2108            let new_height_raw =
2109                inset_relative_size.height.maybe_sub(margin.top).maybe_sub(margin.bottom) - top - bottom;
2110            known_dimensions.height = Some(f32_max(new_height_raw, 0.0));
2111            known_dimensions = known_dimensions.maybe_apply_aspect_ratio(aspect_ratio).maybe_clamp(min_size, max_size);
2112        }
2113        let layout_output = tree.perform_child_layout(
2114            child,
2115            known_dimensions,
2116            constants.node_inner_size,
2117            Size {
2118                width: AvailableSpace::Definite(container_width.maybe_clamp(min_size.width, max_size.width)),
2119                height: AvailableSpace::Definite(container_height.maybe_clamp(min_size.height, max_size.height)),
2120            },
2121            SizingMode::InherentSize,
2122            Line::FALSE,
2123        );
2124        let measured_size = layout_output.size;
2125        let final_size = known_dimensions.unwrap_or(measured_size).maybe_clamp(min_size, max_size);
2126
2127        let non_auto_margin = margin.map(|m| m.unwrap_or(0.0));
2128
2129        let free_space = Size {
2130            width: constants.container_size.width - final_size.width - non_auto_margin.horizontal_axis_sum(),
2131            height: constants.container_size.height - final_size.height - non_auto_margin.vertical_axis_sum(),
2132        }
2133        .f32_max(Size::ZERO);
2134
2135        // Expand auto margins to fill available space
2136        let resolved_margin = {
2137            let auto_margin_size = Size {
2138                width: {
2139                    let auto_margin_count = margin.left.is_none() as u8 + margin.right.is_none() as u8;
2140                    if auto_margin_count > 0 {
2141                        free_space.width / auto_margin_count as f32
2142                    } else {
2143                        0.0
2144                    }
2145                },
2146                height: {
2147                    let auto_margin_count = margin.top.is_none() as u8 + margin.bottom.is_none() as u8;
2148                    if auto_margin_count > 0 {
2149                        free_space.height / auto_margin_count as f32
2150                    } else {
2151                        0.0
2152                    }
2153                },
2154            };
2155
2156            Rect {
2157                left: margin.left.unwrap_or(auto_margin_size.width),
2158                right: margin.right.unwrap_or(auto_margin_size.width),
2159                top: margin.top.unwrap_or(auto_margin_size.height),
2160                bottom: margin.bottom.unwrap_or(auto_margin_size.height),
2161            }
2162        };
2163
2164        // Determine flex-relative insets
2165        let (start_main, end_main) = if constants.is_row { (left, right) } else { (top, bottom) };
2166        let (start_cross, end_cross) = if constants.is_row { (top, bottom) } else { (left, right) };
2167
2168        // Apply main-axis alignment
2169        // let free_main_space = free_space.main(constants.dir) - resolved_margin.main_axis_sum(constants.dir);
2170        let offset_main = if let Some(start) = start_main {
2171            start + constants.border.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2172        } else if let Some(end) = end_main {
2173            constants.container_size.main(constants.dir)
2174                - constants.border.main_end(constants.dir)
2175                - constants.scrollbar_gutter.main(constants.dir)
2176                - final_size.main(constants.dir)
2177                - end
2178                - resolved_margin.main_end(constants.dir)
2179        } else {
2180            // Stretch is an invalid value for justify_content in the flexbox algorithm, so we
2181            // treat it as if it wasn't set (and thus we default to FlexStart behaviour)
2182            match (constants.justify_content.unwrap_or(JustifyContent::Start), constants.is_wrap_reverse) {
2183                (JustifyContent::SpaceBetween, _)
2184                | (JustifyContent::Start, _)
2185                | (JustifyContent::Stretch, false)
2186                | (JustifyContent::FlexStart, false)
2187                | (JustifyContent::FlexEnd, true) => {
2188                    constants.content_box_inset.main_start(constants.dir) + resolved_margin.main_start(constants.dir)
2189                }
2190                (JustifyContent::End, _)
2191                | (JustifyContent::FlexEnd, false)
2192                | (JustifyContent::FlexStart, true)
2193                | (JustifyContent::Stretch, true) => {
2194                    constants.container_size.main(constants.dir)
2195                        - constants.content_box_inset.main_end(constants.dir)
2196                        - final_size.main(constants.dir)
2197                        - resolved_margin.main_end(constants.dir)
2198                }
2199                (JustifyContent::SpaceEvenly, _) | (JustifyContent::SpaceAround, _) | (JustifyContent::Center, _) => {
2200                    (constants.container_size.main(constants.dir)
2201                        + constants.content_box_inset.main_start(constants.dir)
2202                        - constants.content_box_inset.main_end(constants.dir)
2203                        - final_size.main(constants.dir)
2204                        + resolved_margin.main_start(constants.dir)
2205                        - resolved_margin.main_end(constants.dir))
2206                        / 2.0
2207                }
2208            }
2209        };
2210
2211        // Apply cross-axis alignment
2212        // let free_cross_space = free_space.cross(constants.dir) - resolved_margin.cross_axis_sum(constants.dir);
2213        let offset_cross = if let Some(start) = start_cross {
2214            start + constants.border.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2215        } else if let Some(end) = end_cross {
2216            constants.container_size.cross(constants.dir)
2217                - constants.border.cross_end(constants.dir)
2218                - constants.scrollbar_gutter.cross(constants.dir)
2219                - final_size.cross(constants.dir)
2220                - end
2221                - resolved_margin.cross_end(constants.dir)
2222        } else {
2223            match (align_self, constants.is_wrap_reverse) {
2224                // Stretch alignment does not apply to absolutely positioned items
2225                // See "Example 3" at https://www.w3.org/TR/css-flexbox-1/#abspos-items
2226                // Note: Stretch should be FlexStart not Start when we support both
2227                (AlignSelf::Start, _)
2228                | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, false)
2229                | (AlignSelf::FlexEnd, true) => {
2230                    constants.content_box_inset.cross_start(constants.dir) + resolved_margin.cross_start(constants.dir)
2231                }
2232                (AlignSelf::End, _)
2233                | (AlignSelf::Baseline | AlignSelf::Stretch | AlignSelf::FlexStart, true)
2234                | (AlignSelf::FlexEnd, false) => {
2235                    constants.container_size.cross(constants.dir)
2236                        - constants.content_box_inset.cross_end(constants.dir)
2237                        - final_size.cross(constants.dir)
2238                        - resolved_margin.cross_end(constants.dir)
2239                }
2240                (AlignSelf::Center, _) => {
2241                    (constants.container_size.cross(constants.dir)
2242                        + constants.content_box_inset.cross_start(constants.dir)
2243                        - constants.content_box_inset.cross_end(constants.dir)
2244                        - final_size.cross(constants.dir)
2245                        + resolved_margin.cross_start(constants.dir)
2246                        - resolved_margin.cross_end(constants.dir))
2247                        / 2.0
2248                }
2249            }
2250        };
2251
2252        let location = match constants.is_row {
2253            true => Point { x: offset_main, y: offset_cross },
2254            false => Point { x: offset_cross, y: offset_main },
2255        };
2256        let scrollbar_size = Size {
2257            width: if overflow.y == Overflow::Scroll { scrollbar_width } else { 0.0 },
2258            height: if overflow.x == Overflow::Scroll { scrollbar_width } else { 0.0 },
2259        };
2260        tree.set_unrounded_layout(
2261            child,
2262            &Layout {
2263                order: order as u32,
2264                size: final_size,
2265                #[cfg(feature = "content_size")]
2266                content_size: layout_output.content_size,
2267                scrollbar_size,
2268                location,
2269                padding,
2270                border,
2271                margin: resolved_margin,
2272            },
2273        );
2274
2275        #[cfg(feature = "content_size")]
2276        {
2277            let size_content_size_contribution = Size {
2278                width: match overflow.x {
2279                    Overflow::Visible => f32_max(final_size.width, layout_output.content_size.width),
2280                    _ => final_size.width,
2281                },
2282                height: match overflow.y {
2283                    Overflow::Visible => f32_max(final_size.height, layout_output.content_size.height),
2284                    _ => final_size.height,
2285                },
2286            };
2287            if size_content_size_contribution.has_non_zero_area() {
2288                let content_size_contribution = Size {
2289                    width: location.x + size_content_size_contribution.width,
2290                    height: location.y + size_content_size_contribution.height,
2291                };
2292                content_size = content_size.f32_max(content_size_contribution);
2293            }
2294        }
2295    }
2296
2297    content_size
2298}
2299
2300/// Computes the total space taken up by gaps in an axis given:
2301///   - The size of each gap
2302///   - The number of items (children or flex-lines) between which there are gaps
2303#[inline(always)]
2304fn sum_axis_gaps(gap: f32, num_items: usize) -> f32 {
2305    // Gaps only exist between items, so...
2306    if num_items <= 1 {
2307        // ...if there are less than 2 items then there are no gaps
2308        0.0
2309    } else {
2310        // ...otherwise there are (num_items - 1) gaps
2311        gap * (num_items - 1) as f32
2312    }
2313}