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