gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definiciones

⎇ 303768a02ae67d8e068c0ff853da0dc08e6f8618 src/compute/flexbox.rs Rust · 2594 líneas · 119.7 KB Historia Quién tocó qué crudo

fit content 303768a02 · Jonathan Kelley ·

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