tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 231Rust 8TOML 3CSS 1317 Definitions

⎇ 2ca609ecf19c2e34bca2ce1fe1f4e73b09d62a97 src/compute/flexbox.rs Rust · 2434 lines · 111.8 KB History Who touched what raw

merge `alignment_based_offset` match cases 7e8d5b1d7 · Kane Wang ·

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