tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 245JavaScript 8Markdown 71218 Sutichaykuna

⎇ b746ac4f346dde8a2b6f85d3a428c02a2342f099 src/compute/flexbox.rs Rust · 2263 siq'ikuna · 104.9 KB Ñawpa willay Pi imata llamkarqan ch'uya

Fix flex line cross-size determination (#690) b746ac4f3 · Nico Burns ·

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