gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 709HTML 662Markdown 12TOML 62348 Definiciones

⎇ 5db1a9d99aeb127615eb3d3dae90d83e7d5eaace src/compute/flexbox.rs Rust · 2344 líneas · 108.6 KB Historia Quién tocó qué crudo

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