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