gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 499HTML 254C 19JavaScript 81372 Definitions

⎇ 54ed355d41a747e0a45ab57b75b399ff24c49387 src/compute/flexbox.rs Rust · 2069 lines · 93.1 KB History Who touched what raw

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