tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 1061HTML 997Markdown 12TOML 83120 Definitions

⎇ 6ce28e1535f2ab4631d0502f2fee13d4827f2a11 src/compute/flexbox.rs Rust · 1902 lines · 82.0 KB History Who touched what raw

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