gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

HTML 5544Rust 88Markdown 11TOML 91926 Definiciones

⎇ d4374b93f0ec2c0ace42d3feafec22d3af1940d8 src/compute/flexbox.rs Rust · 2106 líneas · 98.8 KB Historia Quién tocó qué crudo

Refactor `LayoutTree` + add examples of custom trees (#564) d4374b93f · Nico Burns ·

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