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