gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 254C 19JavaScript 81412 Definiciones

⎇ e2b3b2401f1f9bc4b4da89a5da1fc95525725891 src/compute/flexbox.rs Rust · 2061 líneas · 92.7 KB Historia Quién tocó qué crudo

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