tawasuyu

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 501HTML 238Markdown 7TOML 31453 Definitions

⎇ c154b8aa719da0d6f8636b123765a47fa40a3306 src/compute/flexbox.rs Rust · 2223 lines · 103.3 KB History Who touched what raw

Ensure that root flexbox node sizes are floored by their padding border (#655) c154b8aa7 · Nico Burns ·

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