tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Definitions

⎇ 3bc35ec3b541eea8ea62344722f155ce9b22e227 src/compute/flexbox.rs Rust · 2284 lines · 105.5 KB History Who touched what raw

Fix wrapping when a max main size style is present (#694) 3bc35ec3b · Nico Burns ·

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