gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5364Rust 86Markdown 12TOML 91841 Sutichaykuna

⎇ a99dc54ca0de208012b0d7b1c0023489bfd1919c src/compute/flexbox.rs Rust · 2119 siq'ikuna · 98.1 KB Ñawpa willay Pi imata llamkarqan ch'uya

Make low-level layout algorithm functions public (#434) a99dc54ca · Nico Burns ·

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