tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 717HTML 669Markdown 12TOML 62372 Definitions

⎇ 7303e42620c9078f46cd0090f6b85e45b399ad4c src/compute/flexbox.rs Rust · 2113 lines · 97.5 KB History Who touched what raw

Fixed clippy lint errors (needless_pass_by_ref_mut) (#517) 7303e4262 · Yurii Nakonechnyi ·

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