tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Definitions

⎇ 7b6efafe42c49ebdf1d84a7afe8ff4e62fda94ed src/compute/flexbox.rs Rust · 2243 lines · 103.9 KB History Who touched what raw

Convert algorithms to traitified styles 7b6efafe4 · Nico Burns ·

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