tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 788HTML 740Markdown 12TOML 62536 Definitions

⎇ d338f3731da519d182bbc074de46382984ab7c4a src/compute/flexbox.rs Rust · 2153 lines · 99.6 KB History Who touched what raw

Minor misspelling fix (RunMode::PeformLayout -> RunMode::PerformLayout) (#485) d338f3731 · Yurii Nakonechnyi ·

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