tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 583HTML 275Markdown 8TOML 41586 Definitions

⎇ bf3dfaec9e387a7e67408fc78ca869d7eaaa8e1f src/compute/flexbox.rs Rust · 2063 lines · 92.8 KB History Who touched what raw

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