gioser

tawasuyu/taffy

A high performance rust-powered UI layout library

Rust 497HTML 254C 19JavaScript 81329 Definiciones

⎇ 477ff4aff498cbae4cdfbf0cda1ee5b04c784f08 src/compute/flexbox.rs Rust · 2364 líneas · 109.5 KB Historia Quién tocó qué crudo

Absolute Insets should be resolved against the container size minus border (#666) 477ff4aff · Julia Ortiz ·

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