gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definiciones

⎇ dab541d6104d58e2e10ce90c4a1dad0b703160cd src/compute/flexbox.rs Rust · 2175 líneas · 100.9 KB Historia Quién tocó qué crudo

Flexbox: pass correct cross-axis available space when computing an item's intrinsic main size (+misc fixes) (#522) dab541d61 · Nico Burns ·

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