gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1111HTML 1039Markdown 12TOML 94524 Definiciones

⎇ devin/1784923726-flow-root src/compute/flexbox.rs Rust · 2553 líneas · 119.0 KB Historia Quién tocó qué crudo

More correct caching logic (#911) df2663aad · Nico Burns ·

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