taffy/compute/
flexbox.rs

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